{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# GNN实例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 实例一"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import scipy\n",
    "import numpy as np\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.autograd import Variable"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据输入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# (node, label)集\n",
    "N = [(\"n{}\".format(i), 0) for i in range(1,7)] + \\\n",
    "    [(\"n{}\".format(i), 1) for i in range(7,13)] + \\\n",
    "    [(\"n{}\".format(i), 2) for i in range(13,19)]\n",
    "# 边集\n",
    "E = [(\"n1\",\"n2\"), (\"n1\",\"n3\"), (\"n1\",\"n5\"),\n",
    "     (\"n2\",\"n4\"),\n",
    "     (\"n3\",\"n6\"), (\"n3\",\"n9\"),\n",
    "     (\"n4\",\"n5\"), (\"n4\",\"n6\"), (\"n4\",\"n8\"),\n",
    "     (\"n5\",\"n14\"),\n",
    "     (\"n7\",\"n8\"), (\"n7\",\"n9\"), (\"n7\",\"n11\"),\n",
    "     (\"n8\",\"n10\"), (\"n8\",\"n11\"), (\"n8\", \"n12\"),\n",
    "     (\"n9\",\"n10\"), (\"n9\",\"n14\"),\n",
    "     (\"n10\",\"n12\"),\n",
    "     (\"n11\",\"n18\"),\n",
    "     (\"n13\",\"n15\"), (\"n13\",\"n16\"), (\"n13\",\"n18\"),\n",
    "     (\"n14\",\"n16\"), (\"n14\",\"n18\"),\n",
    "     (\"n15\",\"n16\"), (\"n15\",\"n18\"),\n",
    "     (\"n17\",\"n18\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据Graph显示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeEAAAFCCAYAAADGwmVOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzs3XdcU1f/wPFPwp4OxF1F66qts+4t2iq4Fbd129a9On+19nme2latVWvVWqgDK1oVqAvcW6mjFayjjrq1Kg5QkJ2c3x8XECQIhBvCOO/X675ibm7O/SaGfHPOPUMjhBBIkiRJkpTntOYOQJIkSZKKKpmEJUmSJMlMZBKWJEmSJDORSViSJEmSzEQmYUmSJEkyE5mEJUmSJMlMZBKWJEmSJDORSViSJEmSzEQmYUmSJEkyE5mEJUmSJMlMZBKWJEmSJDORSViSJEmSzEQmYUmSJEkyE5mEJUmSJMlMZBKWJEmSJDORSViSJEmSzMTS3AFI+ZdOB5cvw717yr8dHeH115VbSZIkKfdkEpbSiYqCX34Bb2+4cAGsrMDCQnlMCIiNhdKloUsXmDwZatc2b7ySJEkFmUYIIcwdhGR+8fHw+eeweDFotfDs2cuPt7RUEnTdurBqFdSqlSdhSpIkFSoyCUv8+Sf06QPh4UpNNye0WrCxURL4xx8r9yVJkqTskUm4iNu5E3r3hpiY3JXj4ABdu4Kf3/Pma0mSJOnlZBIuwg4fhk6dcl77zYy9vVKj9vUFjUadMiVJkgozmYSLqCdPoGpVePxY3XLt7WHFCujfX91yJUmSCiN5Ba+IGjs2903QhsTEwLvvwv376pctSZJU2MgkXARduACbNkFcnGnKj4uDr74yTdmSJEmFiUzCRdDChZCYaLryExJg5Ur1rjVLkiQVVjIJFzFJScpkHElJ2Tk6FugFlAM0ydv1F44ZA9QGHAEXwBM4h0YDmzerFbUkSVLhJJNwEXPhQk7G8iYAfwKNX3LMz4AzMDD5djvQiaioOA4ezE2kkiRJhZ9MwkXMn3+mvZdSu10M1ACcgCEoyRegGHATWP2SEo8CxwAfYH/yvjvAeY4cUS1sSZKkQkkm4SLm2jWIjn5x7xdACyAJ8AN+yUGJLdL8OyV5a4Fy3L5tbJSSJElFg1zAoYgx3FlqGdAXECi13lAjSo4Ghif/ezpQzqSdvyRJkgoDWRMuYuztDe1tkHxbPPk2Q1U5Cw8Bd+B3lI5acwCwts55fJIkSUWJTMJFTLVq4OT04t6UBhFj5pq8AbQETgKfAN6p5VSubFyMkiRJRYVsji5iGjUCvT4nzxjO82u9AB+gDEeaB5RCuSb8L1AJZUjTlOTjBtG6dZPchitJklSoySRcxFSvntPlBn1fuB+QfPsflCT8b/L9m8D3qUdZW9elbdvGGFe7liRJKhrkAg5F0PTpsHixMrOVqWi1T3nttXa8//5IBg8eTIkSJUx3MkmSpAJKXhMugiZMyGltOGdsbGD6dCcWLZrHkSNHqFKlCkOHDuXw4cPI33ySJEnPyZpwETVuHKxaZZr5nUuUgKtXoXhyZ+uHDx+yevVqfHx8ABgzZgxDhw6lVKlS6p9ckiSpAJFJuIiKiVGuD//7b9bH5oS9PWzcCJ6eGR8TQnDkyBF8fHzYsmULHh4ejBkzhnbt2qE1ZdVckiQpn5JJuAgLDYU2bQzNoGUce3t47z2YPz/rYyMiIvDz88Pb25uYmBhGjx7N8OHDKVu2rDrBSJIkFQAyCRdxv/8OnTrBs2c5HbqUnoMDjBgBixaBJgcdooUQnDhxAh8fHwICAnB3d2fMmDG89dZbWFhYGB+QJElSASCTsMTly9Cvn3L77FlOn52IjQ38+KMVw4fnLAG/6OnTp6xbtw4fHx8ePHjA6NGjGTFiBBUrVjS+UEmSpHxMXoiTqF4d/vgD/vc/pTNVxhm1MrK1VbaWLZ9SokRzvLyicpWAAZydnXnvvff4448/2LRpE3fv3qVu3bp069aNLVu2kJS9RZAlSZIKDFkTltJJTIQtW+Cnn+DUKYiKUpItgE4HSUlQsyb07Anvvw/lysGwYcMoW7Ysc+bMUT2eZ8+esWHDBnx8fLh58yYjRoxg1KhRuLm5qX4uSZKkvCaTsPRSDx9CeLiSgB0clPmgX7xUe+/ePerUqcPhw4epVauWyWI5e/YsPj4++Pn58eabbzJmzBi6d++OtVwpQpKkAkomYUkVCxcuJDg4mJ07d6LJbbt0FmJjYwkMDMTb25uLFy8ybNgwRo8eTfXq1U16XkmSJLXJa8KSKsaPH8+///7Lb7/9ZvJz2dnZMXjwYA4ePMjBgwfR6/W0atUKd3d31q1bR1xcnMljkCRJUoOsCUuq2b9/PyNGjOD8+fPYG1642GQSEhLYvHkz3t7ehIWFMWTIEMaMGUPt2rXzNA5JkqSckDVhSTXt27enWbNmzJ492+DjQghiE2N5EveE+KR4Vc9tbW1N37592b17N8ePH8fe3p6OHTvSqlUrfH19iYmJUfV8kiRJapA1YUlVt2/fpn79+hw/fpxXX32VqxFXWRW2ij1X93A2/CwxiTFoNVp0QkdJ25I0LNcQj+oevFP3HVzsXVSNJTExkaCgIHx8fDh27BgDBw5kzJgx1KtXT9XzSJIkGUsmYUl1c+bMYdupbWg7aznx7wl0eh2J+sRMj7eztEMg6F6zO9+9/R0VndWfnOPmzZusWLGCFStWULZsWcaMGcOAAQNwys6gaEmSJBORSVhSVXxSPJ/u/pSFIQsRljn7aFlqLLGxtGFh54WMajDKJL2sdTodO3fuxMfHh4MHD+Ll5cWYMWNo1KiRyXt1S5IkvUgmYUk1kXGRuPu6c+HhBWKTjF8j0cHKgZ61euLb0xcLrenmj7579y4rV67k559/xtnZmXfffZfBgwdTrFgxk53TECHgxg0IC4MnT5SpP11coEEDKF8+T0ORJCmPySQsqSI6IZoWy1tw8dFFEnQJuS7P3sqeXrV68UuvX0xeQ9Xr9ezbtw9vb2927dpFz549GTNmDC1atDDZuYWA48dh3jzYsUO5b2mpLKIhhDIhSkIC2NiAlxdMnQqvv26SUCRJMiOZhCVVDA4cTOD5QOJ06o3RtbeyZ95b8xjbeKxqZWYlPDyc1atX4+Pjg6WlJWPGjOGdd97BxUW9TmNnzsCgQXDtGsTGZr16laUlWFlBkyawejVUqqRaKJIkmZlMwlKu7fhnB3029CEmUf1hQA5WDpwddxa34m6ql/0yQggOHz6Mt7c327Ztw9PTkzFjxtCuXTuja8d6PcyaBbNnQ1ycUuPNCUtLpWb8/fcwapRRIUiSlM/IJCzlil7oqTi/Inej75qkfAuNBR7VPdg6cKtJys+Ox48fs2bNGry9vUlISGD06NEMHz6c0qVLZ7sMvR7eeQc2bYLcDlm2t4fp05VVryRJKtjkZB1Sruy6souohCiTla8TOnZf2c296HsmO0dWSpYsyaRJkzhz5gy+vr5cuHCBmjVr0rdvX3bt2oU+q/ZkYNw4dRIwKGV8952ySZJUsMmasJQrb/3yFnuu7sn6wEQgALgNRCfvmwyUSP73NcDX8FMte1syc9JMPm/7eS6jVc+TJ09Yt24d3t7eREREMGrUKEaMGEGFChUyHBscDH37qpOA07Kzg2PHoG5ddcuVJCnvyJqwZDQhBCG3QrJ3sA74F8hsyI0z0DTN1uD5Q0nFkgi6HJSLSNVXrFgx3n//fU6dOoW/vz+3b9+mTp069OjRg23btpGUlAQoQ47eeUf9BAzKdeV+/ZQ1niVJKphkTVgy2rWIa7zx4xtKh6z/JO/0AI6j1HZrAj0AyzRPigXmJP87bU34RceB7UBZ4H2lg1bUp1H5ekKN6Oho1q9fj4+PD7dv32bUqFHEx09h0aISxBo/bPqlHB1h1Sro08c05UuSZFqyJiwZ7cLDC1hprdLvPAC8AuiBM8BfRhQsUJIwQHPlJkmfxMOYh8YFmkccHR0ZNWoUx44dIzg4mIcPI5g7N9FkCRggOlrpbS1JUsEkk7BktJjEGAQvNKR0BXoBKRNLGNNp+hLwGHB8Xo6l1jJXs3Dltbp169K//yIcHFyNLGEpUBuwB1yATkCYwSPPnoVbt4w8jSRJZiWTsGQ0KwurjDvLJt/aJt8aM3nWseTbxqQ2ZQshMta687ljxyAhwZjm8wPAeOAi4AlUBHahtO1nZG0NJ08aGaQkSWYlk7BktIrOFcnQpSC3n6j7KD2lLYFGz3cn6hMpZV8ql4XnrQMHlKknM9Ikb4uBGoATMITnv1iuJN82APwBv+T7d1C6macXFaUkfEmSCh6ZhCWjvVH6DeKScjBN5W9A2k7Ou5L3PUuzLyWZ1AEcnu+uUqKK4Zp3PnbjRlZHfAG0AJJQEu0vyft7ANWBU4AXMBglaX8KZHwPhIDLl1UJWZKkPCaTsGQ0awtrXi35avafcBo4m+b+38n7UiqAz1A6cwE0e36YVqOldaXWuYjUPLIeOrQMWAX0S74fmnxbEiXxalEGV/8FVAXaZ1qS4Rq3JEn5nWXWh0hS5sY3Hs8nez7h2X+epX/AI3lL6z9ZFOYAzMi4287SjvfefM/oGM3Fzi6rI1IGQxdPvk2ZxeQnlDerMbAD5ddKK6AbcAPI2Czv4JBhlyRJBYCsCUu5MrTeUPQi62kbc6OCcwUaV2hs0nOYQtYzWaX8Bn6x89a55NvXUGrFjVCaoWNQknB61tbw5ptGhylJkhnJJCzlirONM9OaT8Peyt4k5dtb2TP/7fkmKdvUWrVSFlvIuZbJt+uAYUAHlA5ZLiiJOT0bGz2NGmXYLUlSASCTsJRrM9vOpLxTeTQZanS5Y2NhQ9caXelSo4uq5eaVtm1zvlyhYiAwH6gGbERpju6IMoVYxqweFRXPjBmdWLhwITdv3jQ+YEmS8pyctlJSxdnwszRf3pzohOisD84GC40F5Z3K89fYvyhuWzzrJ+RD169fp2lTLeHhlUx2Dq0WvLx0DB26g4CAADZv3ky1atXo06cPffr04dVXc9BxTpKkPCeTsKSakFshdFrTKdeJ2FJrSRmHMvw+6ndeKfaKStHlnT///JN58+axa9cuWrVayJ49g4mJMU2jk709HDr0/JpwYmIiBw4cwN/fn02bNlG+fHm8vLzo06cPtWrVMkkMkiQZTyZhSVV/3f+LHr/2IDw6nJiknC8dZK2xplmlZmzw2kAZxzImiNA09Ho927dvZ968eVy5coUpU6YwevRo4uMTqVUrksePK6P2YAQbG+jdG9auNfy4Tqfj8OHDBAQEEBgYSPHixVMTcp06dfL1YhiSVFTIJCypLj4pns/3f84PJ35AgyZbcz47Wjui1+mx2G3BjU03KFEis+WV8pf4+Hj8/Pz47rvvsLa25sMPP6Rv375YWVmxadMmxo0bR+fOY1m/fgYxMeomPRcX+OcfKJ6N1nq9Xs+xY8cICAjA398fGxub1CbrN998UyZkSTITmYQlk3kU84jZO2ez4OgCtMW02FnZoRd6hBBoNUrzbFxSHLVK1eLDFh/S9/W+TB4/GVtbW77//nszR/9yjx8/ZtmyZSxevJh69erxwQcf4O7ujkaj4dGjR0yaNIkTJ06wcuVKWrVqxebNMHAgqq2o5OAAe/dC06Y5f64Qgj///DM1IScmJtKnTx+8vLxo2rQpWq3srylJeUUmYcmkZs+eze3bt/nmu28IvRfK9cjrJOoSsbW0pWapmtQpXQcbS5vU4x89esRrr73Gnj17qJv1QNs8d+3aNRYuXMgvv/xC9+7dmT59OnXq1El9PKX2279/f7766ivs04xR8veHYcMgJuet9KksLJTrwNu3Q8uWWR+fFSEEZ86cSU3IT548oXfv3vTp04dWrVphYWGR+5NIkpQ5IUkm1KRJE7F79+4cPefHH38UrVu3Fnq93kRR5dzJkydFv379hIuLi/j444/F7du30z3+8OFDMXDgQFGtWjVx+PDhTMs5dUqIqlWFsLcXQhnAlP3NwUGIxo2FuHLFdK/z/Pnz4ssvvxT169cXpUuXFu+9957YtWuXSEhIMN1JJckUEhKE2LxZiOnThWjUSAhXVyGKF1duGzcW4oMPhNiyRTnOjGQSlkzm9u3bomTJkjn+Ak9KShINGjQQfn5+Joose3Q6ndi6dato27atqFSpkpg/f754+vRphuMCAwNFuXLlxNSpU8WzZ8+yLDc+XogvvxSiRAkhnJyyTr6OjkKUKyfEjz8KodOZ4pUa9s8//4g5c+aIJk2aCBcXFzFixAixbds2ERcXl3dBSFJOPX4sxKefClGsmPIHptUa/sPSapXHixcX4rPPhIiIMEu4MglLJrN06VIxZMgQo54bEhIiKlSoYDDpmVpsbKzw8fERtWrVEg0aNBBr1641+EPiwYMHYsCAAaJ69eovrf1mJiFBCH9/IXr1ihcazXVhYaEX1tZCWFsLYWUlRPXqQgwbJsTu3XmbfA25ceOGWLBggWjZsqUoXry4GDx4sPjtt99ETEyMeQOTpLS2blV+3drY5KyZydZWiJIlhQgOzvOQZRKWTOatt94S/v7+Rj9/+PDh4oMPPlAxopd7+PChmDVrlihbtqzw8PAQ+/bty7RJPKX2O23atGzVfl9m//79okWLFiIhQfkx/uSJEImJuSrSpP7991+xZMkS0b59e+Hs7Cz69u0r1q9fL6KioswdmlRU6fVCTJxo3HWetJu9vRBTpyrl5RGZhCWTiIiIEE5OTiI6OtroMu7duydKlSolzp8/r2JkGV25ckVMmDBBlChRQowYMUKcOXMm02PT1n6PHDmiyvkXLFggxo8fr0pZeS08PFz4+PiITp06CScnJ9GzZ0/xyy+/iMjISHOHJhUVer0Q776b+wScNhGPHZtniViORZBMIigoiHbt2uGQizX2ypQpw4wZM5g0aRLCBJ34T5w4Qb9+/WjSpAlOTk6cPXuWFStW8MYbbxg8PjAwkDp16lC+fHnCwsJoqUb3ZCAsLIz69eurUlZec3V1ZfTo0ezYsYMbN27Qs2dPNmzYwCuvvEKXLl1YsWIFjx49MneYUmG2bBmsWZO7YQdpxcTA6tXg46NOeVnJk1QvFTleXl5i+fLluS4nMTFRvPHGG7lq1k5Lp9OJLVu2iNatW4vKlSuLhQsXZnnd+cGDB6J///6iRo0a4ujRo6rEkVbdunXFyZMnVS/XnJ48eSLWrl0r+vTpI5ydnUXHjh3FsmXLxL1798wdmlSYXLumDBtQowZsaDjCjRsmfwkyCUuqi42NFc7OziI8PFyV8g4cOCAqVaqUq6bt2NhY4e3tLWrWrCkaNmwo1q1bJxKzceHV399flC1bVkybNs0knZDi4uKEnZ2diI2NVb3s/CI6Olr4+/uLAQMGiGLFiok2bdqIRYsWZRjmJUk59tZbQlhYmCYJW1oK0bmzyV+CnKxDUt22bdv49ttvOXjwoGplDho0iKpVqzJr1qx0+3U6uHgR7t2DpCRwdITatZ9P5fjo0SOWLl3KkiVLaNSoER988AFt27bNcprGhw8fMmHCBEJDQ1m5ciUtWrRQ7bWkFRoaytChQzlz5oxJys9v4uLi2L17N/7+/mzdupWaNWumzmft5uZm7vCkguT6dXjtNYiLM905bG3h0iV4xXQLychrwpLqNm3aRM+ePVUt89tvv2XZsmX8888/REXB0qVQv74yfWPTpspCBv36gYcHlC4Nrq5J1Kp1nCpVenH9+nX27dvHtm3baNeuXZYJOCAggDp16lCxYkXCwsJMloBBScIF9XqwMWxtbenWrRu+vr7cu3eP//znP1y4cIHGjRvTqFEjvvnmGy5dumTuMKWCYPFi0Otz/LRTQDegImALVAY+AuINHazXw5IluQgya7ImLKlKp9NRrlw5jh8/TpUqVVQt++uvv2P58le4e7cvWq2GZ89efrxGo8PWVkPNmlpWrYJ69V5+/IMHD5gwYQJhYWEmrf2mNWnSJNzc3Jg2bZrJz5WfJSUlcejQodQVn1xdXVPns65du7ZcYELKqFo1uHIlx09bBUwAOgBOwAYgEZgOzDP0hJo14cIFo8PMiqwJS6oKCQmhfPnyqifg0FDw8ZnGtWtdiY3NOgEDCGFBbKyW06eheXP4/HOl+doQf39/6tatS6VKlUxe+02rIPeMVpOlpSXu7u4sWbKEO3fusHTpUiIjI/Hw8OC1115jxowZhIaGmqSXvFQAxcfDzZsGH9Ikb4uBGiiJdgiQkPx4S+AasBlYA/xf8v5dmZ3r6lVITFQlbENkTVhS1fTp03F2duaLL75Qrcy9e6FHD7KVeF/G3h7c3SEwEKyslH0ptd/Tp0+zcuVKmjdvnvuAs0mv11OiRAmuXbtGyZIl8+y8BYkQgpMnT+Lv709AQABA6hKMTZo0kTXkourMGWUFk6ioDA+lfCJKojQ7rwfigJ+BUQaK+j/gG8ADCDZ0LicnOH5cuf5sArImLKlGCKH69eDff4fu3XOfgEEZ/rd3LwwYoHR/TFv7DQ0NzdMEDMqKTMWLF5cJ+CU0Gg1NmjRh7ty5/PPPPwQEBGBtbc3w4cOpXLkyU6ZM4fDhw+gya+KQCqeoKMhiyc1lKE3P/ZLvhxo45iiwAOXa8CwDjwPKeQwke7VYmqxkqVBKSoK7dyEhAaytoVw5sEz+FJ05cwa9Xq/aEoRRUdCzp3pj8EFZz3fHDkGTJsuIivqewMDAPE++KWRTdM5oNBrq169P/fr1mTVrFufPn8ff358JEyYQHh5Or1698PLyok2bNlhayq+2Qi0bS2w2SL5NHihB9AuPBwN9UWrOm4CGmRUkRLbOZyz5SZWy9PffSm/kffvgn3+UpKvVKh0Hk5Lg1VehQwcQ4ii9evVSrYlw8mR4+lSVotKJidFw+vRIzp0bTvXqduqfIJuKWs9otdWuXZuZM2cyc+ZMLl++TEBAAB9//DHXr1+nR48eeHl54e7ujrW1tblDlVQW5+yMVVwcL0uNKcnN0LfRL8BIwBnYBrz0Z3hiojLkwkTkNWEpUydPwrhxcO6c8jlMSsr8WAsL0OvjqF49EV9fJ5o1y925//lHx+uvQ0KCaX6BWlrC0KGwfLlJis+Wrl27Mnr0aNWHcxV1169fJzAwEH9/fy5cuEC3bt3o06cPb7/9Nra2tuYOzzAhlM5GFhbKh1Ne6071+PFjwsLCCA0NTd2uXb3Ko/h47Aykr5R37hrgBkwBvgeGoTRP7wI6AyL5tmaa5y40FICjo1IbMNH/iUzCUgbx8fB//wc//qg03+aUnR2MGQNz5ihj3XMiNjYWX19fPv3UhidPBiOE6WoxdnZw/77S78IcKlasyJEjR+QkFSZ0584dAgMDCQgIICwsDA8PD/r06YOHh0eu5jXPtcRE2LRJ2Y4fVyaeAKV5ydZWmXGmTRsYMgQaZtpQWqgIIbh161Zqok1JvJGRkdSrV4/69evToEEDGjRoQO3atbFu00Z5716QVRJeBYzILAZDO1u0gKNHc/XaXkYmYSmdp0+VpuVz54xLwCns7KBWLdi/H4oVy/r4Bw8esHTpUpYuXUrTps3YuzeAmJjsXC2JBQYBx4B7yftS/vxSxAEfovSTjEK5+jMfB4em/PADjMjsL9KEHjx4QI0aNXj8+LHs4ZtH7t+/z6ZNmwgICOD48eN07NgRLy8vunTpgrOzc94EERsLs2fD998rCfdlHX60WiUhV6kCX3+t9FAsJJKSkrh48WK6ZBsWFoaNjU26ZFu/fn2qVq2K1lAnrOXLYcoUiH7xaq+KHB2VSUGGDTPZKWQSllLFxUHr1krv/3iD08fkjI2N0qv/6FFleJAhly9fZv78+axfv56+ffsydepUrKxqUa9edntEPwHqAPWBrcn7XkzC7wM/AW8kb+sBR+Aq77xTitWrjXhxubR7926+/vpr9u/fn/cnl3j06BFbtmwhICCAQ4cO0bZtW7y8vOjevTslSpQwzUl//12Z1u3Ro5z/wrW3h7feUhKPi4tp4jORmJgY/vrrr3RNyufOnaNChQrpkm2DBg0oU6ZMTgoGV1d1e26+yNERwsOVWoWpmHx2aqnAGD9eCDs7dedAt7VVlvp80dGjR0WvXr2Eq6ur+Pzzz9OtrvPrr0I4OaWUQfL2g4DqAhwFDBYQ/8K5ItIcey3N/vsCrARok/8tBAxJPu4LUa2a6d5PvV6Iw4eFmD1bmQe+alUhKlZUbqtXvypatNgmDhwQQqczXQxS1iIjI8WaNWtEr169hLOzs3j77beFt7e3aguQCCGE+Pnn3P9xWVsL4eoqxMWL6sWlsocPH4rdu3eLuXPnioEDB4rXXntN2NnZiYYNG4pRo0aJH374QRw5ciTLlcuy7bPP1FtH2NC6wjNnqhPnS8gkLAkhhDhyRP0EnLLZ2Qlx4IAQSUlJIiAgQDRv3lxUrVpVLF682ODKSN98k3ZhlJTEWlLAMAG2yfd/zmYS3pe8zy3NvoXJ+3oIe3v138uYGCEWLhSiQgUhHB2V705D74uFRZJwdBSibFkh5s4VIheLREkqiYqKEhs2bBD9+vUTxYoVE+3btxeLFy8Wd+7cMb7QFSvUSxQajRAlSwpx5Yp6L9oIer1eXLt2TQQGBoqZM2eKbt26iYoVKwpnZ2fRpk0bMXnyZLFq1Spx+vRpER8fb7pA4uOVX7Vqf2lpNEJUry5EQoLpYk8mm6MlQOkH8vffpivf1TUSZ+fGlCrlwocffkjPnj2xyGTs3f/+B88n3Eq5XroBZVTfMGA1MB5lYroUkUBKM+I1njdH/woMRGmGTlmp6GdgDNAUa+tjqjS9p0hpcXz8OGetZHZ2yrXzX3+Ftm3Vi0cyXmxsLDt37iQgIIBt27bx+uuvp87WValSpewVkjJnam46WLxIq1XmTT579vnUbyaUmJjIhQsX0jUnh4WF4eDgkOH6bZUqVfK+j0NoKLRqpW6ztIMDhISASnMevJTJ07yU7508aboWnee1vlixbFmo0Ov1WcYzZ46ylGf6mvDl5PuTku8Py0VNeIFJasJff5371gQ7OyFmzFCasqX8Iy4uTgQFBYmRI0cKFxcX0bhxYzFnzhzxzz//ZP6khASlNqXRqP8HZW8vxOefq/46o6MwOC1BAAAgAElEQVSjRUhIiFiyZIkYPXq0aNSokbC3txc1a9YUAwYMELNnzxY7d+4U9+/fV/3cubJzp3pfYvb2QuzenWehyyQsiXfeEUKrzc7nM0ZATwFlM0l4KQmujlCuwSrXXUEpf8CA7MUTECCEs/OLSTjlPJNzmITviefXhO8l7xuUfNxM8dpr6ryHM2eq+x0wfbo6cUnqS0hIELt37xbvv/++KFOmjKhXr5748ssvxfnz59Mf+MMPQjg4qJ+AUzZbWyFu3zb6dYSHh4udO3eK2bNniwEDBoiaNWsKe3t70ahRIzF69GixZMkSERISIqKionL5juWRQ4eUpnobG+PfTxcX5dpcHpJJWBIVK2b3cxop4BUB3V6ShIcIaCugcrokDEKUK5e9eG7cSFujzE4SHiZgYJpj+yTve5D8+Jjk/a8L6C9AI5QOXuFi1Kjcv38bNqjfkmBvr1xKlPK3pKQkcfDgQTFp0iRRoUIFUbt2bfH555+LsNBQoX/lFdMlYFCSzYwZWcao1+vFlStXhL+/v5gxY4bo0qWLKF++vChevLho166dmDp1qli9erX466+/REIeXAM1qYgIIQYMEPGWliIpuy0QlpbKF86gQUJERuZ5yPKacBEXHQ0lS6as1JVyLecHYBFwF+gBrADSTpqR2fXXtHqiLBb2BfAfQLl89eBB1uOGhVBGHjx6lDamlPO8OPQ+7TEvSnlOLPABynXllHHC3+Ho2Bxvbxg48OXxvEx4OFSvbprpNR0dlWVMK1RQv2xJfXq9nhMnTuDv788tPz+W37+PYy6+XjsDO5P/HYoyCC+DEiXg4cPUxQwSExP5+++/080udfr0aZycnDIMB6pcuXKhHKP+9OlTOleqxI7OnXHeskWZgUyvTz/m0cFBec90OmVClClTTLZKUlbk3NFF3JUrSqeg9MtlfsHzRcD8gPYYXgQsZ+zslLmn33zz5cdpNMp0md9+C3FxL36JLSTj5HJZfdHZAUuSt/R69criqVmYMEHdPjdpxcUpM48FG1xfTcpvtFotzZo1o1mzZghHR5g1K/MFrLOwGNibjeN0cXGs++ILDty9S2hoKH///Tdubm6pibZr167Ur1+fUqVKGRVHQfTTTz9RpUsXnP38lC+2c+fgzz/h0iWl85a9PdSsqXwR1a6dJ53bXkYm4SIuLs7QlKjLUHoiC5SeyIYWAcs5jUYQF5e9X95jxypJ2FSsrWHUqJxPq5nW/fuwdavp1vtOSlJmHLt5E7LbGVfKHzQHDxpMwNlpa7oAfAR8jvJz+GVi4+OJCwmhUd++jB49mrp162Kf2cw4RUBcXBwLFixgx44dyg4rK6hfX9nyKZmEizhr6+Qrr+lktQiYcZ48eUr79l0oVeoqJUqUoGTJkpQoUSLdv9Pe9ulTl4CAcsTFqb/stZ0dfP557srw9jZ2TvfhgG8mj6X/z9DrYckSZR5uqQC5cOGlD2fW1pQIvAPUAz4j6yTsKASjmzWD99/PbcSFwi+//EL9+vVVW041L8gkXMRVrqzUhtN72SJgxrO2LsalS7uxtHxMREQEERERPH78ON3thQsXUv/98GEUSUnrgAqAeolYo4nh668jcHHJ3cVWf39jm6Lf5vkPHIATwO/AqxmOTEiAgACZhAucLAafZ9bW9F+UmnAYvHSZvlRCwJMnRodZmOh0OubOnctycy6NZgSZhIu4kiWVjlIPHmT3GcOBhDT3P0CZh3keUAplIowjwKnkxzcB14GeODr2pHJlO6ACFbLZ2+j8eWWuA7U6Pmm1sRQr9hvTpo0iKKgDDRo0yLQ2XqJECezt7Q12XtHp4OLFzM6SVaPjoOQtRaPk20kGS7t5U/lOt7HJ6auVzMbQggNpZNbW5AcUAya/cPwklFpxB0OFyQ8GAL/99huurq60bt3a3KHkiEzCEs2bw5Yt2T36xWbUgOTb/6Ak4SMvHHM6eXOjadOcr5tbu7ZyXdTdXenJbWQ/FwDs7PR06vSUd9914fjxT1iwYAGJiYm8/vrrnD17NrUGnrZmrtPpDCZojaY6ev3HpO81/qLsdHA7DPyJ8tVreDknW1uldbNePeNfu5THypVL6d5vUGZtTQK4k7yldRi4ZaggW1uQS2EihGD27NnMnDmzwPX4lklYYsIE2LcPoqPV6Im8iudDh55zcoKJE42Lr2FDCAuD/v2Vjo7ZW13pOUtLpbKwYIGW0aPLoNF0xsOjM6NHj6ZXr17cu3eP5cuXG+zQEhcXZ7Dp/K+/LNFq9VmcOTsd3FLe31GA4YWNtVrTDIGSTKhVK2VayRy6/sL9lHSS6RAla+ushxsUAXv37iU2NpauXbuaO5QcU7/Hi1TgdOgApl5O1c4OOnUy/vlubsq8zHPnQqlSSlLPio2NsnXqpMyLPWZM+o5UFStW5NChQ1hZWdGyZUtu3LiRoQxbW1vKlStH7dq1adWqFd26dWPYsGEMGTIYG5usulZn1cHtOspYagvg5b9QsmjdlPKbVq2y9yHNrbg42UQCzJ49m48//tjwusP5XMGLWFKdVqusMW6qkQ0ODrBgQe4TiVarjB++dw/8/KBLFyhTRhmF4OSkbPb2SuKtVw8+/hiuXoVt2+CVVwyXaWdnh6+vL0OHDqVZs2YcOHAgW7G4umZnaFJWHdx+AHQoE5u4ZVqKTqf88JAKkO7dlTFmL0iZ1s0t+f7C5PurMikm5XiDtWCNBjp3Vv7AirCTJ09y6dIlBuZm1h0zkjNmSam6doXdu5UeuWqxslKu527fbuxwnqxFRipjdpOSlO+jSpWMS/h79uxh8ODBfP7554wfP/6l15aEUFoPog2O3srOLF/RQEXgCcoVv1aZnsvGRpljoAD+yC/axoyBVasMJmNVODoqM7kUsI5IavPy8qJ169ZMnvxid7aCQf5ZS6lWrVJqeJmsMJhjWq1Sg1u92nQJGKB4cWUCnNdfV5qtjU1WHTt25Pfff+enn35i9OjRxL9kmIlGk9tVzlahJOA3eVkCBuW1yQRcAH32mXLN1hQsLJQPfKuXf3YKu0uXLnHo0CFGjx5t7lCMJv+0pVSlSilLaKY08eaGpSWULg1Hjyq3BUXVqlX5/fffefLkCe3atePu3buZHjtypFIZySg7jY4Tkvf98dJ47O2Vmb2kAsjNTRngbYrmYhsbZfHpAtYTWG3ffvst48ePx6EAN8nL5mgpg/BwGDQIjh3LeU9kUL5zGjVSviPKllU/vryg1+v5+uuvWbZsGQEBATRt2jTDMTExSsuBmmuJv8jWVrkGntWiF1I+pdcrPQOPHlVvknE7O/jxRxg2TJ3yCqg7d+5Qp04dLl++jIuLi7nDMZqsCUsZlC6tXBv+8UdlkZbsdvJ0clKahhctUsb2FtQEDMpk/DNmzGDp0qV07dqVlStXZjjG3l4ZdmWqDm22tkotWCbgAkyrVQbhN2yoJM9cigFuvPdekU/AAAsXLmTYsGEFOgGDrAlLWUhMVBYpGDo0lNjY17C0tMDOzgohlJawxESlk1KDBjB1KvToYfZFSVT3999/06NHDzw8PJg3bx5WaV5gfLxyzdbA6KZcK1cOLl8u8p1fC4f4eKVr/6+/Gtd0YmUFtrb8OXYsXVev5vDhw1SrVk39OAuIiIgIXn31VU6fPs0rmQ19KCBkEpaydPfuXWrXro0QsGPHZZ48KUV8vNLn5NVXla2wdxyKjIxk4MCBxMfHs2HDhnRLw/3xB7Rtq26ztJ0d7NpV5PvdFD779qEfPJhn9+5lMjXLCywtlQTcurXSc7JcOX766SfmzZtHSEgIrq6uJg44f/rqq6+4fPkyq1atMncouSaTsJSlFStWsGrVKvR6PUeOHDF3OGaj0+n47LPPWL9+PZs2baJemkkStm6FAQPUScR2dsr3bb9+uS9Lyn9mf/UV1vv2MS0xUel4YWurNCfpdErzkqWl0sRkaQkDBxpccH7GjBns2bOHffv2FbmlC2NjY6lSpQr79u2jdu3a5g4n12QSlrLUp08fIiIi8PT05IMPPjB3OGb366+/MnHiRJYsWUK/NJlyzx7w8lL63xgz1lqr1aHRxPPbb7Z061bImxaKqISEBNzc3Ni+fbvyIy4pSVml5MwZiIpShh6VLq1cQ65YMdPez0IIhg8fTkREBIGBgVhaFp0ZiJcuXcquXbvYtGmTuUNRhUzC0kslJCTg6uqKra0tR48eLdLXodIKDQ2lV69eDBo0iC+//BKL5MHVDx4oQ5f27VNmFNRnNb00yvesvT00a6bn6dOe9OvXRv7YKaR8fX1Zs2YNu3fvznVZCQkJdOnSherVq7NkyZICt3CBMZKSkqhevTrr1q2jWbNm5g5HFfLntvRShw8fpmLFipQuXVom4DQaNGjAyZMnCQkJoXv37jxJXtPV1VVpmt63D/r0UVoanZ0zrjZnbf18f8+esHMn7N6tZcOGRcydO5dTp04ZOKtUkAkh+O6775g+fboq5VlbWxMQEEBISAhzisiC0xs2bKBSpUqFJgGDXEVJykJQUBAlS5akXbt25g4l33F1dWX37t1MmzaNJk2asHnzZmrVqgVA06awYYPSwhgaqnTeOnNGaaq2tVUmO2rUSGl1TDsEyc3Nje+//56BAwdy6tSpAj0JgZTenj170Ov1dMrNSiYvcHZ2Jjg4mBYtWlChQgXeeecd1crOb1KWKyxsPzhkc7T0UjVq1CA+Pp7AwEDelEumZWrFihV88sknrFixQpXl1IYNG4aNjQ3e3t4qRCflB507d6Zfv36MHDlS9bLPnTuHu7s7fn5+dOzYUfXy84Pg4GA+/fRTwsLCClfTu5CkTFy+fFmUKlVKvPLKK0Kv15s7nHwvJCREVKhQQcyaNSvX79fTp09F1apVRUBAgErRSeZ05swZUbZsWREXF2eycxw4cEC4urqKsLAwk53DnNq0aSPWrl1r7jBUJ68JS5kKCgqicuXK9OzZs3D98jSR5s2bc+LECbZu3Uq/fv2INrzEUrY4OTmxdu1axo4dy+3bt1WMUjKH+fPnM378eGxe7BygorZt27J48WK6du3KrVu3THYecwgJCeHWrVv07dvX3KGoTiZhKVNBQUFERkbSs2dPc4dSYJQvX54DBw7g5OREixYtuHbtmtFlNW3alEmTJjF06FB0Op2KUUp56d69e/z222+MHTvW5Ofq168fU6dOxcPDg8jISJOfL6/MmTOHDz/8sFAOxZLXhCWDoqOjKVu2LNbW1oSHhxfKD78pCSFYvHgxX331FX5+fnTo0MGocnQ6He7u7nh4ePDJJ5+oHKWUF2bMmMHjx49ZunRpnpxPCMHUqVMJCwtj586dJq1954Vz587RoUMHrl27hp0K82/nNzIJSwZt2rSJTz75hKZNm+Lr62vucAqs/fv3M3DgQD755BMmT55sVLP+zZs3adSoEUFBQTRu3NgEUUqm8uzZM9zc3AgJCaF69ep5dl6dTkf//v2xsrLCz88PbQGeV3bYsGHUrFmT//u//zN3KCZRcP9nJJMKCgpCr9fTq1cvc4dSoLVv355jx46xcuVKRowYQVxcXI7LqFSpEkuWLGHQoEG5us4s5T1fX19atmyZpwkYwMLCgjVr1nD79u0C3YJy8+ZNtm3bxrhx48wdisnImrCUgRCC8uXLExUVRXh4eJGbm9YUnj17xsiRI7l+/TqBgYFUqFAhx2WkDG1ZsWKF2uFJJqDT6ahVqxYrVqygdevWZonh8ePHtGzZknHjxjFx4kSzxJAbU6ZMwdramrlz55o7FJORNWEpg7CwMIQQdOzYUSZglTg4OPDrr7/Ss2dPmjRpQkhISI7LWLRoEUeOHGHjxo0miFBS29atWylRogStzLgUVsmSJdm+fTuzZ88mMDDQbHEY4+HDh6xevZopU6aYOxSTkklYyiAoKAgnJyfZFK0yjUbDp59+ire3Nz179uTnn3/O0fMdHR3x8/Nj/Pjx3Lx500RRSmpJmaLS3MP73Nzc2Lp1K++99x5Hjx41ayzpPHwIJ07A4cPK7cOH6R5evHgxXl5elC9f3kwB5hFzDVCW8q8mTZoIe3t78fDhQ3OHUmhduHBB1KxZU4wbN04kJCTk6LmzZ88WrVu3FklJSSaKTsqt48ePi8qVK4vExERzh5Jq+/btokyZMuLChQvmCUCvF2LvXiF69BCiVCkhrK2FKFbs+WZtLYSLixDdu4uYrVtFKRcXcfHiRfPEmofkNWEpnQcPHuDm5kbjxo05cOCAucMp1J48ecKQIUN4+vQpGzdupHTp0tl6nl6vp2PHjnTo0IHPPvvMxFFKxujfvz/NmjVj6tSp5g4lnZUrV/Lll18SEhJC2bJl8+7EW7fCuHEQGQnZ6FyYYGNDlEaDy/r10L17HgRoPrI5Wkpnx44dlCpVit69e5s7lEKvWLFibN68mdatW9O4ceNsr5yk1WpZvXo1ixYt4tixYyaOUsqp69evs2fPHkaNGmXuUDIYMWIEw4cPp0uXLnnT0z4yEvr2hQED4PbtbCVgAOv4eFzi4mDgQGU5skI08ciLZE1YSqdfv35s376ds2fPUrlyZXOHU2T4+/szduxYFi1axMCBA7P1nMDAQD788ENCQ0NxdnY2cYRSdk2dOhVLS0u+/fZbc4dikBCCMWPGcOfOHbZs2YKVlZVpTnT/PrRsqSTf+Hjjy7G2hgoVICQE8rL2nkdkEpZSJSUlUaJECdzc3Dhz5oy5wyly/vrrL3r27ImXlxfffPMNFhYWWT7n3XffJT4+Xk6okk9ERkZStWpVTp8+zSuvvGLucDKVmJhIjx49KF++PD4+Pup3HnvyRFmn8+ZNSErKfXmWlvDKK3DqFBQvnvvy8hHZHC2lCgkJwd7enn79+pk7lCKpbt26nDx5kj///JMuXboQERGR5XMWLFjA8ePHWbduXR5EKGXFx8cHDw+PfJ2AAaysrNiwYQNhYWH873//U/8E770Hd+6ok4BBKefOHXj3XXXKy0dkEpZSbdu2jYSEBLlggxm5uLiwc+dOXnvtNZo0acL58+dferyDgwNr165l8uTJXL9+PW+ClAxKTExk0aJFTJs2zdyhZIujoyNBQUH4+vqqOwFMcLDSESs3TdCGJCRAUJBSdiEim6OlVK+++irx8fHcunXL7GMbJWXKww8//BAfHx969Ojx0mPnzZvHb7/9xsGDB+ViG2bi5+eHj49PgRtVcPHiRdq2bcuqVavo3Llz7goTAtzclGZoU6lYUSm/kHxHyZqwBMCNGze4d+8e/fv3lwk4nxg2bBjbtm1jwoQJ/O9//0Ov12d67LRp07C3t+err77KwwilFEKI1Mk5CpqaNWsSGBjI0KFDs91DP1OHDsHjx+oElpnISChgP3ReRiZhCVBmybKxsZGzZOUzTZo04cSJE+zYsQMvLy+ioqIMHqfVavH19eXHH3/MX7MiFREHDhwgJiaGLl26mDsUo7Ro0YKffvqJbt265WoNbBYtgmfPcvy0WKAXUA7QJG/XMzs4Olo5TyEhk7AEwMaNG9Hr9TRv3tzcoUgvKFeuHPv378fFxYXmzZtz5coVg8eVL18eb29vhgwZwpMnT/I4yqJt/vz5TJ06tUAvGdirVy8+/fRTPDw8ePTokXGFHD2qNEnnUALwJ5DthToL0Q/NgvuJkVQTExPD77//To8ePbI1LEbKezY2Nnh7ezNu3DhatGjB7t27DR7XvXt3PDw8GDt2LLK7R964cOECJ06cYOjQoeYOJdcmTJhA9+7d6d69O7GxsTl7cmRkpk3RKbXbxUANwAkYgpJ8AYoBN4HV2T3Xkyemb/bOIzIJS+zfvx9bW1s5NCmf02g0jBs3jo0bNzJ06FC+++47g4l23rx5hIWFsWbNGjNEWfQsWLCA999/Hzs7O3OHoorZs2dTqVIlhgwZgk6ny/4Tr12DLN6DL4AWQBLgB/xibJC2tnD1qrHPzldkEpbYuHEj8fHxdOjQwdyhSNnQpk0bjh8/jp+fH0OHDs1QY7G3t2fdunVMmzaNq4Xkiyq/evDgARs2bGD8+PHmDkU1Wq2WVatW8fjxY6ZNm5b9FpWEhCx7LC8DVgEpP/dDjQ1So1HOVwjIJFzECSHYunUrbdu2xdbW1tzhSNlUqVIljhw5gk6no3Xr1ty6dSvd4/Xq1eOzzz5j0KBBJCYmpnssKQnCwuDnn5U5Ffr0AS8vmDwZ/Pzg4kWjLusVSUuXLsXLyyvbi28UFDY2Nvz222/s3buX+fPnZ+9J1tZZfnAaJN+mzHll9OzVQoCNjbHPzl/MsXSTlH+cOXNG2NnZiTVr1pg7FMkIer1ezJ07V5QrV04cOnQo3WM6nU506tRJzJgxQwghxN27QsycKUTJkkI4OQlhby+E8m2mbBqNEI6Oyv7KlYVYvFiIp0/N8KIKiJiYGFG6dGlx/vx5c4diMjdv3hQVK1YU69aty/rgyEghrKzSf6iSN5K3a8n3JyffH/bCcREGjjW4WVkJ8fix6d+APCCTcBH33//+V1hZWYnIyEhzhyLlwo4dO0Tp0qXFjz/+mG7/3bt3RZky5cTEiZeFvb0QtraZf6+9uDk4CFG8uBCbN5vpReVz3t7ewtPT09xhmNzp06eFq6urOHDgwEuPi4uLE3ElSxqdhIeBGJjm2D7J+x4Y+nCWKZNHr970ZBIu4l577TXRsGFDc4chqeDSpUuidu3a4t133xXx8fFCCCEePhSiRo3HQqOJznbyfXGztxdiwAAhkouUhNLKUKtWLbF3715zh5In9u7dK0qXLi3Onj2bbv+NGzfEsmXLRPfu3YWzs7PY4+IidBqNUUmYTLYMNWKNRojevc30TqhPXhMuwiIiIrh8+XKhGFohQfXq1Tl27Bj379/H3d2d8+fDadwYrl0rgRAORpcbEwObN0PnzoWmL0yubd++HVtbW9q3b2/uUPKEu7s78+fPx9PTk4CAAD755BPq1KlDw4YNOXz4MP379+fq1at02LQJrb19huenZFS35PsLk++vMnDMi5sbL3BwUDowFBJy7ugibM2aNYwcOZLr169Tvnx5c4cjqUSv1/PFF7OYM6cvQtQkKUmd39p2dtCjB8gFm5SkNHLkSIYMGWLuUEzu3r177Nixg+DgYLZu3YpGo2HixIn06tWLxo0bp59bQAioVs20w4cqV1aGQxWS6XVlTbgIW7VqFRUqVJAJuJDRarVYWs5Eq62uWgIGiI2FLVuUrSgLDQ3l0qVL9O/f39yhmIROp+P48eN88cUXNGrUiFq1ahEUFISHhwdXrlxh+PDhnDp1ioYNG2ac3EejgWXLwEBtWBX29vDTT4UmAYOsCRdZOp0OR0dHJk+ezOzZs80djqSiCxeU9dRzOuFRdpUoATdugJOTacrP74YMGULdunX56KOPzB2Kah4/fszOnTsJDg5mx44dlClThi5duuDp6UmLFi2wsrJKPVan09G7d2+KFSuGr6+v4QVfhg5Fv349WjWvX9jYKOPp/PzUKzMfkEm4iDp69Cjt2rXj7Nmz1KxZ09zhSCoaMQJ++QVyMtlRTjg4wNy5MG6cacrPz27fvk3dunW5evUqxYsXz/oJ+ZQQgtOnTxMcHExwcDB//fUX7dq1w9PTEw8PDypXrvzS58fExODu7k6HDh0MrtwVeugQju7uVAUs1PggWllBlSpw8iQ4O+e+vHxEJuEiauTIkWzevNn4idqlfOnpUyhb1thasC/wHfAP4AKMBj7H0FWrQnZZLts++ugjEhISWLhwoblDybGnT5+yZ88egoOD2b59O3Z2dnTp0oUuXbrQpk2bHE/W8+DBA1q0aMH06dN5//33U/fv3buXgQMHsmrePDy/+QauX4e4OOMDt7FRPnBHjoCrq/Hl5FNy9e9CLCI2glN3T/H3w7+JSYzBUmtJReeKvFnuTbYFbcPT09PcIUoq27ULLI36q94IDEeZSn8wEAL8B7AFPs5w9IMHysxatWoZG2nBExUVxfLly/njjz/MHUq2CCG4cOFCam33xIkTtGjRAk9PTz7++GOqV6+eq/JdXV3ZsWMHrVq1okKFCnTr1o2NGzcyfvx4Nm7cSNu2baFXL5gwAfz9lW72OWVvD717w5Ilha4GnEIm4UImKj6KX/76he9+/45bT25hZ2VHgi6BJH0SWo0WWwtbEvWJxI6OJbpKNBcfXqRmKdkcXVgcO5bZcq4pVdYfgEXAXaAHsAKwBjYkPz4VZZr9MJRJBucA03nxq8LCAv78s2gl4eXLl9OhQweqVKli7lAyFRMTw4EDBwgKCiI4OBidToenpydTpkyhffv2ODo6qnq+V199lc2bN9OlSxdGjBiBn58fu3fvpl69esoBTk7g6wtDhsD778P9+0oyflkDrEajJN/SpeHHH6FTJ1Vjzm9kc3QhIYRg2R/L+HD3hwA8S8x6YW0rrRWWWks6V+uMTzcfXOxdTB2mZGLNmsHx44YeSUnCJYFuwHogDvgZGAW8A6wBvFCapdcCY5KfcwWomr40DUycCN9/r/ILMIfYWGUy7T/+gHPnlF8xdnbKL4xGjaBBA5Ls7KhWrRrr16+nadOm5o44natXr6bWdo8cOULDhg3x9PTE09OT119/3XDHKRUJIRg8eDAbNmxg165duLu7Z3Yg/P47LFqkNC2Hhyvvs0ajPBYbqzQ3t2oFkyZBixZF4nqHTMKFwP3o+/Ra34u/7v+VreT7IhsLG+ws7VjntY7O1TqbIEIpr1SrBleuGHok5ctsA9AXGIayeut4lFVeTwGtAEMXk4+iLECXXt++sGFDxqMLjNOn4bvvYONGZfGBhIT01y5tbJQtPp7b9erxZWwsP50+bfbEkJCQwOHDh1Nru5GRkXh4eODp6clbb72Vpx3GdDodkyZNIiQkhEGDBuHt7U1ISAiu2bl2GxWldCyIj1fe5ypVimSXe9kcXcDdeXqHpj835f6z+yTpk4wqI14XT7wunt7re7O8x3IGvjFQ5SilvJL1T7nwzqcAACAASURBVOrM1rFpCFxEuTb8GCUh90ZJyoa/UE3V+9rkIiKU5aOCgpQEoNMZ7jgUH69sQLkTJ1hiYwNvv610PS9bNk9Dvn37Ntu3byc4OJh9+/ZRu3ZtPD098fPzo0GDBmi1eT/lQ3x8PEOHDiU8PJwDBw5QrFgxnjx5QteuXdm/fz/2WY0VdnKCunXzJth8TCbhAuxp/FNarmjJveh76ETuvxFjk2IZtXkULnYuvP3q2ypEKOW1YsWyOiLlT/7F2lwSUBGYlnx/JUoCrgxUM1hSqVJGhWhehw8r037FxKQm2OywAOX4gwehRg1Yuxa6djVZmElJSRw7diy1mfnWrVt06tSJPn364O3tnb2apglFRUXRu3dvnJ2dU6fwBPjyyy+5desWAwYMIDAwEEvjegkWKXLGrAJs0vZJ3H92X5UEnCI2KZYB/gOIiI1QrUwp7zRpYuwzLwA1UK4P90YZnqQB5pIxYStjhRs3NvZcZrJnjzIBdkREjhJwOomJSjNqv37w66+qhhceHs7q1asZMGAApUuXZuLEiWi1WpYuXcr9+/dZu3YtQ4YMMXsCfvDgAe7u7lStWpUNGzakG9qk0Wjw8fEhLi6OCRMmIK92Zk0m4QJq37V9bDy/kbikXIy/y0RMYgxjg8aqXq5kei1bgnEdYEskb+uBYKARsAnoZ/DouLgYzp3z5e+//y4YX7RnzjyvAashNhZGjoRDh4wuQq/Xc/LkSf773//StGlTatSowebNm3nrrbc4c+YMoaGhzJo1ixYtWuSbGuX169dp1aoVnTt3ZtmyZRmnrQSsra3x9/fn2LFjcja+bJAdswqoN73f5NTdUyYr39bSlr/H/41bcTeTnUNS3927Sv8WYyt62eXomMDAgVPYvn0r1tbWqZM+tG3bNseTPphcYiK88QZcvpydi+Y5U6YM/PNPtn/5REREsGvXrtTpIV1cXFKnh2zZsiXW1tbqxqeiM2fO4OnpyUcffcTEiROzPP7ff/+lefPmzJo1i3feeSfjATod7N+v9JQ+cEBZ9CEhQekkV7UqtG2r9JR2d1fGxBVSMgkXQOfCz9HYpzGxSSaaHBiwtrBmYpOJzHt7nsnOIZlGly6wfbv6+SaFrS188gl88YUyPOXMmTMEBQURFBTEmTNnaNeuXWpiqVixommCyIkvv4TZs9WrBadlawtDhyqLChiQ8v6kXNsNCwujTZs2qdND5ucxx2kdPXqU3r17s3DhQgYOzH7HzfPnz9O+fXv8/Pzo2LGjsjM6WhmmtHCh0iEuNhaSDHQqtbRUhjDZ2sKUKcqYuELYe1om4QLo490fM//3+SSJLHpDJwIBwG2ed4KdjNLqmOIusAf4N/n44kATZXOxc+HhRw/VDV4yuZAQeOst0+QcUOZRuHLFcAfhR48esXPnToKCgti5cycVK1ZMrSU3bdrUYPOlScXHK2NPo6JMdw5bW7hzB0qWBCA6Opq9e/emJl4rK6vUHyXt2rXDzs7OdLGYwLZt21In4nj77Zx32Dx06BBeXl7KJB6PHsGgQcr8qjmZW9XOTknA69YpNeNCRCbhAqjpz005cedE1gfGAUuBssCl5H0vJuEFwBOgNMp0wX8n7x8GNtVsuDHlBmUcy6gUuZRXhg1TxvDmZspeQxwc4KuvsremelJSEsePH0+tJd+5c4fOnTvTpUsXOnXqRMnkpGVSfn7KTE3R0VkfayRhb8+DCRPwK1uW4OBgjh07RrNmzVInzKhRo4bJJ8wwFV9fXz7++GM2b96cq0lKNqxfz73Ro5mQlIQ2Nx9KOzvlw/f112Yfr60WmYQLGCEETt84KZNy/Cd5pwdwHKW2WxNlNsK0/ThiUWYfhPRJWAfMAgQwFigD/ETqjIbFmhVjbZ+1eFaXc0wXNE+fQvXqyhzPav2FW1tD/frKpEfGDEu9desWwcHBbNu2jYMHD1KvXr3UWvIbb7xhmkTVoQPs25fjp10HDDUUfwt8YGD/JUtL5o0Ywf+3d+fxMZ3t48c/M8lkmRBbECKWUlsVsSvSKqW2WmprHqJFn6rtQX9KaGm1tlq+WpRu2ia22EtiTdQWRFBbaS1V+741ZM/cvz9OopZEtplMZnK9X6+8mJlz7nNNSK4593Ldbdu2pUWLFhS0g27T6dOnM3v2bDZt2kTVnNYn/eADEmbPxikxMeeBGY3aB6sZM3LeVh4gSdjGJCQn4PK5Cwr1bxJ2RVtd8jvacs830GovpEovCQNsBPaiJeCiaHfCJYG3wc3djS9f/5J+dfpZ6N0IS/rjD2jUSHHvniKnCyGcnMDLS6vsaI4b2NjY2Ic1jkNDQzGZTLRt25b27dvTvHnzjAs9ZFbRotqSpCz6Gy0JVwMe7YDtCDRP43hlMKCLicnu7hl5ilKKUaNGERISwubNm3M+rv/zzzBoUHpFzbPHaIQ5c7R9O22cLFGyMakbMTymPdAZeCHl8ZUsNFgVbRz4GloC1qc85wwmZSLRZIZPrsIqqlaFrl1nYDDcxcUl+5+1jUaoUgX27TNPAgZwdXWlTZs2zJkzh7/++osNGzbw3HPPMW3aNDw9PWnXrh1ff/01586dy/5FbtxId2Bcl/I1B+3za0GgF/DkFvQNgFmPfKWVgAF0Li5w4kQ6r9qOpKQk3nnnHXbu3MnOnTtznoAvXdJ2UTJnAgbt33XoUK19GydJ2Ma4OKbcBT8qdYJM6sqQJ3+TpCcGWATcBd5B27HOE9gO7AcHvQMFnMy764rIPQsWLGDbtvn8+aeiRw8dRmPWhtEcHLQhuFGj4OBBy1XI0ul0VK9enZEjR7Jt2zbOnz9Pnz59iIyMpH79+tSoUYNRo0axY8cOktKaRZueGze0W/hnGI9WFTsJ7Uch6InXV6D9WJUFhgD/pNeQXg83bXsSY0xMDJ07d+b69euEhYVRrJgZNnQZMMD8ExNSxcdr5UdtnCRhG6PX6fEq6PXEk9ls7A7ajGg94IXWrZ1ajOcm6NBRvXj1bDYurGnbtm0EBAQQEhJChQrF+Okn2LJFW77k7Jz+sla9XpuE6uqqrbz57TcYNy53e1kLFy5M9+7d+fnnn7l69So//PADzs7ODB8+nBIlStCzZ0+CgoK4mVHSM5ky/NQxH/iJf0uS/PbIa8+hdTC9hZZ85wADeAabLaatrV9u1aoVhQsX5pdffsHNzS3njV68qP2ny8oHp6xITITwcO06Nsz2BzDyoQZeDbjwz4XMHbwabQJWqs1o28e2Qku4rmhjxj+jjQkfTTmuLMQlxVGjRA0zRS1yy6lTp+jRoweLFy9+bELNSy/BunVaQY/wcIiI0LY9vHdPS77FikGzZtCwobZPQV7YQ12v19OwYUMaNmzIhAkTuHz5Mhs2bGD16tUMHjyY6tWrP5zcVbt27ccnd7m7Z5gA0tvOohzaBo6pegKvo9UQM5HG516l8sY3LBsuX75M69atee2115g+fbr5NoP4+utsnxoLjEOr33YV8AD6os0jfYxSMHcuTJ6c7WtZmyRhG9Shcgc2ndnEfTKx7OLwE49Th61eAdyA/wBb0caRr6Al4npADajtWRsnh7xbwUc87c6dO7Rv357PPvuMFi1apHlMqVLaHuu9euVycGZQunRp+vXrR79+/YiPj2fHjh2EhobSvXt3YmJiaNu2Le3ataNly5YU8PbOcGp4ettZnAdKA4Ynnk83PcXEwAsvpPdqnnXy5Elat27NgAED+PDDD807Q33VqmyVblNo1cs3ovVG9EFbRXk6rYPj42H1aptOwjI72gbFJMZQYlqJbO0dnFmOyY5MaTyFD9p8YLFrCPNKTEzk9ddfp1atWsycOdPa4eS6kydPPpxtvW/fPho3bkzgiROUvPB0r1FqqjkLlAeGAV+i/cL/CW3hwfeAL+CM1qF0D217i+/Turi3N5w/b863Y3H79++nQ4cOfP755/TrZ+YVEImJ2qLyNJYkpX7vZwNf8XBFJAvQOunCgZZo80N/49+pLukyGLSJX4YnPzLZBhkTtkFGg5G+Pn1xdnC22DWcHJyY/u50OnTowJ49eyx2HWEeSikGDRqEq6sr06ZNs3Y4VlG5cmWGDx9OWFgY586do1GjRgQ+eEB2Pqq+CtRAKya3BG3k5iO0xPEUgwE6dcp23NYQFhZG27ZtmT9/vvkTMGh1up2f/fspvUlx4Sl/FgBqoc1cf4WnO/UecnGBkyfTezXPkyRsoz5r/pnFZi4bDUYW91jM2dNnadu2LX5+fjRv3pywsDDb2DEnH/q///s/9u7dy5IlS3K/NGQecuzYMUaNGsWLL75ISEgI7iNGYEwjGaiUr/Ipj2elPP4p5bEvWnfodbTCc6eAz9CmUDzFwUFbLmMjli1bhp+fHytWrKBjx46WuUh0dIabLqQ3KS51ut1+tHXa9dEWbLSDtD9Q6fWWLUtqYZKEbVQhl0Is6rIIo8FMRQ1SODs483rF1+lYtSMuLi68//77nDx5kr59+zJ06FAaNmzImjVrMJlMZr2uyL5169YxY8YMQkJC7KJSU1Zdu3aNWbNmUadOHV5//XV0Oh2bNm3iwIEDvDd2LLo338xwqVK2OThoM9kqVbJM+6lOnoQFC6BfP20j5xo1wMcHunWD2bMhKipTpdHmzZvH8OHD2bJlC76+vpaLNxOTu9KbFJe6QKM62kS4zYA7cAk4kF5jNvzBU8aEbdznOz5n8s7JxCTlvFq/k4MTzxd9nj399lDQ+elf5iaTiV9++YWJEycSGxtLQEAAPXv2zDN7neZHhw8fpmXLloSEhOSotq+tiY2NZe3atQQFBbFr1y46duxI7969ad68+dM9ATduaDU8790zfyBGIxw7pu0faW4mkza5acoUOH5cS2xpFb1wcdHWkBUtCiNHavscP1FxTCnFp59+ysKFC9m8eTPPPfec+eN91N9/axPV0iiWktF4fDDabPTq/FsEsBjaMrEDPF4MELDsv0EukCRs45RS1BpSi+PFjpOsz/46RaPBSOVilfm1z68Udin8zGOVUmzZsoVJkyZx/vx5Ro0aRZ8+ffLePrJ27sqVKzRq1Ihp06bRvXv3jE+wcSaTiYiICAIDA1m5ciV169bF39+fzp07UyCj/XxXrYLevc27tZTRCF98oZVkNLczZ6BHD632aFaqTRmNUKgQBAdr682A5ORkhg4dyp49e9iwYQMlS1p+Q5a42FgMRYrgkMbs6IyScBLapKwzaBO2otEWcNQGokhjSY/RqG3QYaMbOkh3tI379NNPcY1yZe1baynhVgIXx6wlQh06XB1dGdZwGJH9IzNMwKBVOGrVqhXbtm0jKCiItWvXUrFiRWbMmMF9C+5WI/4VGxtLx44d6d+/v90n4FOnTjFu3DgqVqzIgAEDeP755zly5Ahbtmyhd+/eGSdggC5d4MMPn7pDzC5lNGpJfeBAs7T3mOXLoWZNrVJKVss9xsRoC8Fbt4YxY4iPi+Ott97ixIkTbNu2zWIJODk5mX379jF58mRatmyJR/HinMhmD5kj2nh8G2ALWumCnkAI6ayprVHDZhMwyJ2wTVu8eDEBAQFERkbi6elJdHw0E3dOZF7UPP6J/kdbW5EOFwet/GWLCi2Y2GIitT1r5yiWQ4cOMXnyZH799VcGDx7M4MGDc2erunzIZDLRs2dPDAYDCxcutNlt8p7l9u3bBAcHExgYyF9//YWfnx+9e/fGx8cnZ+93yhSYMCFre9k+IU6v5wdHR7a8/jrt2renbdu2eHl5ZXxiZixaBO++m6P4UimjkTVFi7Kwfn0WLV5s1p4qpRQnTpwgPDyc8PBwtm/fjpeXFy1atKBFixa8/PLLFAoOhhEjzF83+lFubjBzJvz3v5a7hoVJErZRu3fvpmPHjoSHh1OzZs3HXpv/3Xx+3vsz1TtWZ/eF3Zy9c5ZEUyJ6nR4PVw/qlq5L8/LNeevFtyhdsLRZ4zp58iRTp05lzZo19OvXj+HDh1OqVCmzXiO/+/jjjwkPD2fr1q12NQSQkJDA+vXrCQoKIiwsjDZt2tC7d29atWqFwZxrQHfv1rp6b9/OWve0i4tW73PRIm7WqcPGjRsJDQ1l06ZNlCtX7mHlrgYNGmRvhvqePdrWi2ZIwKniHR0xzJyJfsiQHLd1/vz5h0l369atODk5PUy6r776Kp6eno+f8OABlChh3iGAJxmNcP26loxtlRI256+//lKenp4qNDQ0zddbtmypVqxYkctRPe7cuXNqyJAhqkiRIur9999XZ8+etWo89iIoKEiVL19eXbt2zdqhmIXJZFKRkZFq0KBBysPDQzVr1kx999136s6dO5a9cEyMUjNmKFW6tFIFCyrl6KiUNr/48S8HB+11Dw+lJkxQ6u7dp5pKTExUO3bsUKNGjVI1atRQHh4eqlevXmrp0qWZfx8xMUqVKZN2DDn9MhqVOnMmy9+iGzduqGXLlqn33ntPVapUSXl4eKju3burb775Rp0+fVqZTKaMG/nwQ+36lnpfI0dm+X3lNZKEbczdu3dV9erV1Zdffpnm61evXlWFCxdWMTExuRxZ2q5du6YCAgJU0aJFlb+/vzp+/Li1Q7JZu3btUh4eHuro0aPWDiXH/v77b/X555+rypUrq0qVKqkJEyaov/76K/cDMZmU2r5dqYkTlXrtNaXKlVOqVCmlypZV6pVXlBo/XqlNm5RKSsp0k3///bf6+uuvVbt27VTBggWVr6+vmjp1qjp27Fj6iWvUKKVcXS2TrBwclGraNMO4o6OjVWhoqBoxYoSqVauWcnd3V+3atVMzZ85Uhw8fVsnJyZn+HjwUF6d9Ty3xvsqVUyo2Nusx5TGShG1IYmKiatWqlRo4cGC6P8xz5sxR//nPf3I5sozduXNHff7556pEiRKqS5cuav/+/dYOyaacOXNGeXp6qg0bNlg7lGy7d++e+uGHH9TLL7+sihUrpt5//321e/fuzN1R2aiYmBgVGhqq3n//fVW2bFlVrlw5NXDgQBUaGvrvB+WYGKUKFLBMokr9cnVV6okPwPHx8Wr79u1q3LhxqkmTJsrNzU29/PLLasKECSoiIkIlJCSY55sQFWX+u2GjUal9+8wTn5VJErYRJpNJDRw4ULVq1UolJiame1zTpk3VunXrcjGyrLl//76aNWuWKlOmjGrdurXavn27Xf8SNofU3o/Zs2dbO5QsS0xMVOvXr1c9e/ZU7u7uqlOnTmrlypUqLi7O2qHlOpPJpI4ePaqmTJmimjVrpgoWLKjatWunwvz9VbKbW5YT0XFQDUAVAuUEqiyooaBi0zre0VGZ+vdXBw4cUF988YVq3bq1KliwoKpbt6768MMP1aZNm9SDBw8s9+bXrTPfnb6rq9aenZAkbCO+/PJLVb16dXU3jTGpVOfPn1dFixZV8fHxuRhZ9sTFxanvv/9eVapUSTVp0kSFhoZKMk5DYmKiat26tRo0aJC1Q8k0k8mkfvvtNzVixAjl6empGjRooObMmaNu3Lhh7dDylNu3b6slS5aoQ6VLZysZ7QT1Eqj+oHqBckWrxjkuneNv6HSqatWqatCgQWrlypXq1q1bufuGw8KUKlRIKSen7CVfJyft/C1bcjduC5MkbANCQkKUp6dnhmNmM2bMUP369culqMwjMTFRLVmyRL344ouqdu3aatmyZSopC+Nv9m7w4MEZ9n7kFZcuXVLTpk1TL774oipbtqwaO3as+uOPP6wdVt5XokSaSYeUr9mgngdVANR/QMWnk6SGpBzfO53XTQaDUjdvWve93rihVMeOWneyg0Pmkq+Dg3b8G29o59sZScJ53OHDh5WHh4eKiIjI8Nj69eurLTb6KdFkMql169apRo0aqcqVK6sFCxaYb0zKRs2ePTvD3g9ru3//vlq4cKFq1aqVKly4sOrbt6/atm1b9ibx5Ef37illMDwzCRcF1QeUS8rj7x855hao/z1yJ1wY1O70klmhQkqFh1v7HWv271fKz08pZ2el3N2VcnF5PFYXF+15Z2ftODueQyJJOA+7cuWKKlu2rFq0aFGGx54+fVqVKFHCJu6YnsVkMqmtW7eqli1bqrJly6rZs2fnmZneuWnDhg3K09NTncnG0hJLS05OVuHh4apPnz6qcOHCqk2bNmrx4sWWHVO0V+fPpztpKTUJL0t57J/yeNAjx5x95DhAvQ7qYnpJ2N1dqZUrrf2OH/fggVK7dyv11VdKvf++Un36aH9+9ZVSERHa63ZOknAeFRMToxo0aKDGjx+fqeMnTpyoBg4caNmgcllkZKTq1KmTKlmypJo8eXKeviM0p6NHj6rixYurnTt3WjuUxxw/flyNHj1aeXt7q1q1aqkZM2aoy5cvWzss23bhQoZJ+FTK46Epj/ukcex1UO+kvN7yWUl41Sprv2PxBKkdnQeZTCb69OlDxYoVGT9+fKbOWbp0KT179rRwZLmrQYMGrF69mrCwMI4dO0bFihX5+OOPuXnzZsYn26jr16/ToUMHZs6cSdOmTa0dDjdu3GD27NnUr1+fFi1akJSUREhICIcOHWLEiBFSDS2nihSBhIRnHpJaL/nJYp2P7qBbHHgt5e/pbm+v02k7LYk8RZJwHjR+/HguXrzIggULMlUn9/jx49y+fZsmTZrkQnS5r0aNGixcuJDIyEhu3LhB5cqVGT58OBcvXrR2aGYVFxdH586d6dWrF7169bJqHCtWrOCNN96gUqVKREZGMnHiRC5cuMC0adOeKpMqcsDNDYoXz/i4NAwFGgL9gd7AgJTnW6d3QkwM1M5ZjXhhfpKE85igoCAWLlzImjVrMl0XODg4mB49eqDPxEbatqxixYrMnz+fY8eOodfrqVmzJu+++y6nT5+2dmg5ppSif//+eHl58emnn1rl+hEREbz33nt4eXkxb948unTpwsWLF1m4cCGtWrXKXj1kkbF69bJ1WmPgPrAEWA2UBj4GZqd3QrFi2jaHIm+xdn+4+NfOnTtV8eLF1bFjxzJ9jslkUpUrV1aRkZEWjCxvunnzpho3bpzy8PBQb731ljpy5Ii1Q8q2zz77TNWvXz/XJzedPn1ajR8/XlWsWFFVrVpVTZo0SZ07dy5XY8j3li/X6lNnZ+1sZr+cnJQaNsza71SkQZJwHnH69GlVsmRJtXHjxiydd/DgQVWhQoV8Xeji3r17aurUqcrT01N16NBB7dmzx9ohZUlwcLDy9vbOtUlOt2/fVt98841q0qSJKl68uBoyZIiKiorK1/+HrCohQanChS2bhF1csrWJg7A8ScJ5wJ07d1TVqlXVnDlzsnzuhx9+qAICAiwQle2JiYlRc+fOVeXKlVOvvvqqCgsLy/OJZe/evcrDw0P99ttvFr1OQkKCWrt2reratatyd3dXXbt2VWvXrs33a7HzjIkTLbfbkMGg1OuvW/sdinTIfsJWlpiYSJs2bahevTpfffVVls5VSlGhQgXWrl0rk2UekZiYyJIlS5g8eTLu7u6MGTOGDh06WGzMXCnF+XvnOXT1EPfi76FDR1HXoviU8nnmfs3nz5+ncePGzJs3jzfeeMMicR04cIDAwECWLl1K5cqV6d27N927d6dIkSJmv57IgcREePFFOHlSS53mVKCA1q7MZM+TJAlbkVKKAQMGcOHCBdauXYujo2PGJz1i79699O3bl99//z1Ts6jzG5PJxOrVq5k0aRIJCQkEBATQvXv3LH+f06KUIupyFNN3T2f9qfUopXB0cMSkTADodXoSkhJwdnTmzepvMqLRCF4o8cLD86Ojo2natCn+/v588MEHOY7nURcuXGDhwoUEBgaSkJBA79696dWrF5UqVTLrdYSZHT0KjRpps5jNxWiE774DPz/ztSnMSpKwFc2cOZMff/yRiIgI3N3ds3z+sGHDKFq0KOPGjbNAdPZDKcXmzZuZOHEily5dYtSoUfTp0wdnZ+dstff79d/xW+XH6duniUuKe5h40+Ogc8DJwYn6pesT2DmQMgXL0KlTJ0qVKsU333xjlg9Q0dHRrFq1isDAQA4dOkTXrl3x9/fnpZdekg9otmTjRnjzTfMkYqMRxoyBsWNz3pawGEnCVrJ27VoGDBjAnj17KFeuXJbPT05Oxtvbm19//ZUqVapYIEL7tHPnTiZPnsyRI0f44IMP+O9//4ubm1umzlVKMWnnJCbunEhcUhyKrP3oOOodcXZw5qV/XiI5KpmNGzdiMBiy8zYA7f9AeHg4gYGBhISE4Ovri7+/P+3bt8/08jaRB23dCp07Q1xchoU80qTTgYsLTJ0KQ4aYPz5hVva9sDSPOnToEP369WP16tXZSsCgJZOSJUtKAs6iZs2asX79etatW8eePXuoUKECn332GXfu3HnmeSZlos+aPkzaNYnYpNgsJ2CAJFMSDxIfEGYIo/aI2tlOwEePHmXkyJF4e3szduxYGjZsyKlTp1i7di1du3aVBGzrXn0VTp2CFi20Yh5Z6ckoUACqVoWoKEnANkKScC67cuUKb7zxBnPnzqVhw4bZbscey1TmJh8fH5YtW8bOnTv566+/qFSpEqNGjeLatWtpHj90w1BWnlhJTGLOuwmVo2L+4flMi5iW6XOuXr3KzJkzqV27Nm3btsXR0ZGwsDCioqIYMmQIxbNZdUnkUSVKwPr12le7dtqdrbs7PFkwRafTnndx0Yp+/PgjHDkCL7yQdrsiz5Hu6FwUExPDyy+/TMeOHfnoo4+y3U5iYiJeXl7s27eP8uXLmy/AfOzcuXNMnz6dRYsW4efnx8iRIx/2Umw6vYkuy7qYJQE/ytXRld39dlPbM+1SgjExMfzyyy8EBQWxZ88eOnbsiL+/P6+88ordV0cTT7h5EyIjtTvc337TxoydnLS73oYNoUEDkN8FNkmScC4xmUx069YNo9FIYGBgjibLbNq0iU8++YQ9e/aYMUIBcO3aNWbNmsW3335Lhw4dGPzBYF7f8Dq3Ym+Z/Vo6dFQsWpHjA49jcNC6pk0mEzt27CAoKIhVSapOhAAAGC5JREFUq1bRoEED/P396dSpU6bHroUQtkOScC4JCAhg586dhIeHZ3tWbqp33nmHWrVqMWzYMDNFJ550584d5s6dy5QdU4hrHEeyPtki1yngVIAf3viBWo61CAoKIigoCHd3d/r06YOfnx+lS6e/zlgIYfskCeeCH3/8kc8//5y9e/fmeOwuPj6eUqVKcfToUby8vMwUoUiLSZkoPaM01x6kPU5sLsZ7RgouLoifnx/+/v7UqlVLlhUJkU/kvGqBeKbt27czatQotm/fbpbJMxs3bqRmzZqSgHPBngt7MjcOnAisBC6ibWsD8D8graJUR1OOBW0fujaQVDiJXcd2UclDimkIkd/I7A4LOnXqFN27d2fx4sVUq1bNLG3KrOjcE3kpkoTkTKzTTAYuo+0l9yz3gFCe+qlzNjjz27XfshWjEMK2SRK2kNu3b9OuXTsmTJhAy5YtzdLmgwcP2LBhA2+++aZZ2hPPtv3cduKT4+ETtK9I4CtgEtrdbFLKgS7ACKDzMxpTwBqgIPDE57H7CfeJvBRpxsiFELZCkrAFJCQk0LVrV9q3b897771ntnZDQ0Np1KiRrAnNJWfvnH38iW2AN2BC61Y+koXG9gLngS48NQikUPx5889sxymEsF0yJmxmSikGDhxIgQIFmDYt88UYMmPp0qX06NHDrG2K9CWZkh5/oj2QWgPhMHAlkw1dA8KA5kA6G9kkmLJRnlAIYfMkCZvZ9OnT2b9/P7t27cLhyeo2OXDv3j3Cw8NZsGCB2doUz+bq6Pr4E54pf6ZWhcxs3jyBNm78N3AOLSkD/AkYgJbgZpA1wELkR9IdbUZr1qxh1qxZrFu3jgIFCpi17V9++YVXXnmFwoULm7Vdkb5anrUefyK7Py2piwBPA6eAf1Ie3wUugEFvoF7petlsXAhhy+RO2EwOHjzIu+++y/r16/H29jZ7+8HBwfTq1cvs7Yr0NfFuwrLfl/GABxkfvBrtbjfVZsAJaIXWDd38iWMP83CJktFglCQsRD4ld8JmcOnSJd544w3mzZtH/fr1zd7+rVu3iIiIoEOHDmZvW6Tv5fIvZ7hX8EOHgWOPPD6R8lwmuqzjk+Jp4NUg6wEKIWyeVMzKofv37+Pr60u3bt0ICAiwyDW+++47tmzZwrJlyyzSvkhfw+8bsu/SPou1r9fp6VqtK8Hdgi12DSFE3iV3wjmQnJxMr169qFmzJqNHj7bYdaRAh/WMbjKaAk7mHd9/lIujCx+89IHF2hdC5G2ShHNg9OjR3Llzh2+//dZitX6vXLnCwYMHadOmjUXaF8/WqWonXij+AnoL/Kg4OzjTvnJ76YoWIh+TiVnZ9P3337NmzRr27t2Lk5OTxa6zYsUKOnTogKura8YHC7O7du0a+lV6qAeYb8UZoC1Lmt9uvnkbFULYFLkTzoatW7cyduxYQkJCKFasmEWvFRwcLF3RVhIWFkadOnVo1aAVK99a+fS64RwwGoyE+IVQxDWtXR6EEPmFTMzKoj///BNfX1+WLl1K8+bNMz4hB86fP0+dOnW4fPmyRe+2xeOSkpL49NNPWbBgAUFBQbz66qsArDqxil6rehGXFIciez82ep0eo8FIqF8ovuV8zRm2EMIGSXd0Fty6dYv27dszadIkiydggGXLltG5c2dJwLno0qVL+Pn54eTkxMGDBylZsuTD17pU68Lufrvptqwbl+9fztw2h49wM7hR1aMqS7supVJR2bZQCJEP74TP3ztP5MVI9l7cyx83/yA+OR6jwYiPpw/1verTqEwjPIweT50XHx9Pq1ataNiwIV988UWuxFqvXj2mTp1KixYtcuV6+d3GjRt55513GDx4MKNHj0637GhCcgLTIqYxY88MkkxJRCdEP7PdAk4FKOBUgI99P2ZAvQHodTIKJITQ5IsknGRK4pc/fmFqxFSOXj+KQW/gfsL9x7oUHXQOuDm5EZ8UT8vnWjLypZH4lvNFp9OhlOKdd97h7t27rFy50qw1odNz+vRpmjZtyqVLl3LlevlZYmIiH3/8MYsWLWLRokX4+maumzgxOZG1f65l+fHlRF6K5MK9CzjoHUCBCRMVClegsXdj/Gr48VrF1yT5CiGeYvdJ+Pfrv9N9RXfO3zvP/YT7mTpHhw6jwUizss34qdNPLJi9gOXLl7Nz507c3HKn0P7EiRO5evUqs2fPzpXr5VcXLlygZ8+euLu7ExgYmKNtIhOTE7mfcB+9To+bkxuOehntEUI8m10n4bn75jJyy8hsT6Rx0jvhgANuIW4cWnUILy8vC0SZthdffJH58+fTpEmTXLtmfrNu3Tr69+/PiBEjGDlyJHq93KkKIXKX3X5Un7prKhN2TCA2KTbbbaTu8Wpqb+JIzBG8yJ0kfOzYMe7evUvjxo1z5Xr5TUJCAgEBAaxYsYLVq1fz0ksvWTskIUQ+ZZcf/YOPBTNhx4Qsz15NT7wpnq7Lu3L46mGztJeR4OBgevToIXdmFnD27FmaNWvGqVOnOHjwoCRgIYRV2V139NX7V6k8u3KGM1azSoeOSkUr8fvA3zE4GHLUlkmZOHnrJIevHuZu3F10Oh3FXIvhU8qH8oXKU6VKFZYsWUK9erK9nTmtXr2a9957j9GjRzN8+HCLlRoVQojMsrvu6P5r+xOXFGf2dhWKS9GXmBIxhY99P876+Uqx/dx2pkVMI/xsOAYHAzp0JJmSAHDUO5KskjGZTDg0cYBS5n4H+Vd8fDwjR45k3bp1hISE0KCB1GoWQuQNdnUn/Pfdv6k2t5pFknCqQs6FuD7yOk4OmS+gcfDKQXqu6MmV+1d4kPAgw0liOqXD1cmVF4q/wOI3F0thhxw4c+YMPXr0oGzZsixYsIDChQtbOyQhhHjIrgYd5+ybk7lN2BOBpcB04JOUrztPHPPjI6+lfs3VupLX/LEmU/GYlImAsACaLmjKqdunnlqbnB6lU8QkxnDgygFqzqvJnH1zMnU98bhly5bRuHFj3n77bVauXCkJWAiR59hVd/SSY0tISE7I+MBk4DJQGjiZwbENH/l7QYhOiCbwcCDdX+j+7EuYkumxogcbTm/I9gxtkzIRmxTLqLBRnL1zlumtpss4ZibExcUxfPhwtmzZwoYNG6hbt661QxJCiDTZTRL+J/4fbjy4oT34JOXJNkAkcB+oAnREe8cuwAggFpiaQcNpbON74PKBDON5d927bDi9wSwztGMSY5h/YD5FXIvwke9HOW7Pnp08eZLu3btTpUoVDhw4QKFChawdkhBCpMtuuqOPXDuC0WB8/MltgDdgAo4CR7LR8JSUr5+BS9pTt2JvER2f/uzrtX+uJfj3YLMtkQItEU/aOSlTHwDyq0WLFtGkSRMGDBjA0qVLJQELIfI8u7kTvhVz6+nx1vbACyl/PwxcyUKDzkBloCBwETgLBAGDwKmoE3fi7lDQueBTp92JvUOfNX3MmoBTxSbF0m15N/4Y/EeWJobZu5iYGIYOHcrOnTsJCwujVq1a1g5JCCEyxW7uhNOc8OSZ8qdLyp+ZGC5+6C3AD+gAvAsUAuKAv1Oul86k8q+jvrbo7OwbMTdYdWKVxdq3NSdOnKBhw4bExcWxf/9+ScBCCJtiN0m4sEthdDwxaSm77y4BSK+3WadtZVfI5emuzmRTMrMiZ1k0Cd9PuM/UXRkNZOcPP//8M76+vgwbNoygoCAKFny6Z0IIIfIyu+mOrlmyZta6gFejzZJOtRlwAlqhJeE5QAW0O+CLwD3ATXsu6X4SnV7vhI+PD3Xq1MHHx4eqVasScTGC+KT4jK+dCKxMaTd1Y6f/AUWeiO8vICYlrtJAS6AU/HnrT/6++zflC5fP/Pu1Iw8ePGDgwIFERUXx66+/UqNGDWuHJIQQ2WI3d8JFXYtSxLVIxgemOgwce+TxiZTnEgAjUAu4lfLcfaAq0AdwA9/KvowZMwZPT09CQ0N58803KVSoEL1G9+JB/IOMr/3oEqn03AXKAT4p8ZxBW9sMGPQGoi5FZf692pGjR49Sr1499Ho9UVFRkoCFEDbNripmDQgZwA+//fCwFKQlFHAqwOw2s3m79tuPPR8dHU2rn1ux99bejJdIpXp0idSTd8KPugx8C+iAj0DnoGNE4xFMbzXdDO/INiil+OGHHwgICGDGjBn4+/tbOyQhhMgxu+mOBhjWaBiBhwMtmoRjYmIodLEQqpZ6rHBGwYIFidY/MZC8DW2G9e9oS6QqAHWycLFI4AbazGyAxoCDNgnt1O1T2X0LNic6OpoBAwZw5MgRduzYQbVq1awdkhBCmIXddEcDVPWoSr3S9XDQOVikfVdHV9oWb8snYz/Bx8eH4OBgkpP/HVhONiU/fkJ7oDP/LpPKyhIpgOPAfrRucXeg7L8vZaoymB04dOgQdevWxc3NjX379kkCFkLYFbtKwgA/d/oZZ0dni7Rd1LUowe8Hc+jQISZOnMhXX31FtWrV+OGHH0hISMDV4Pr4CTlZIgXwDjAW6Ik2W3sZD2tcF3Sy75nASinmzZvHa6+9xieffMK3336Lq6trxicKIYQNsbskXKFIBaa/Nv3p6lk5ZHQ0Etw1GKPBiE6no127duzatYvvv/+e5cuXU7FiRQy3nthnOLvf3US0Kl8ABqAS2gxpE3AXnBycqFfafvcavnfvHj169ODbb78lIiICPz8/a4ckhBAWYXdJGGBAvQH0r9PfbInY1dGVee3n0aRsk8ee1+l0+Pr6snHjRtasWUPy2eTM3+2uBkIfebw55bkHaEuXZgLLgRDgGyAebZZ0KS0ee03C+/fvp06dOhQvXpw9e/ZQuXJla4ckhBAWY5dJWKfTMav1LIY3Go6rY/a7MPU6PUZHIz92/BH/Ws+ejVu3bl2Cpwbj7JrJrvBnLZEqCBRDWyd8EK1SV3W0JVIuEJ8cT/3S9bP4bvI2pRRfffUVbdu2ZcqUKcydOxcXF5eMTxRCCBtmV0uU0rLz3E56rujJ3fi7WSrm4WZwo0qxKgR3C6ZS0UqZPq/R942IvBSZnVAzxUHnQM8aPVnYZaHFrpHb7ty5Q9++fblw4QLBwcFUrFjR2iEJIUSusMs74Uc1K9eMU0NP8UXLLyhXqBwFnArg4pj2HZabwQ1XR1fqeNZhQccFRP03KksJGCCgaQBuBjdzhJ4mJwcnPmj8gcXaz22RkZHUqVOHcuXKERERIQlYCJGv2P2d8KOUUuy/vJ/IS5FsP7edM7fPkJCcgIujCzVL1qSJdxOalWtG5WLZH4dUSuH7ky97L+41+3plvUlPt+rdWNpjqVnbtQalFDNnzuSLL77gm2++oVOnTtYOSQghcl2+SsK55cK9C1SbW40HiZkoYZkFriZXii0qxuIfF9OsWTOztp2bbt26xdtvv82NGzdYunQp5cuXt3ZIQghhFXbfHW0N3oW8WdF9RY4mhT2pgKEAuwbsYv6X8+nevTvjx48nKclylcEsJSIiAh8fH6pUqcKOHTskAQsh8jW5E7agkJMh9FjRg9jE2LT3O84ER70jRoORLb230MCrAQBXrlzB39+f2NhYFi1aRLly5cwZtkWYTCa++OILZs2axffff0/79u2tHZIQQlid3AlbUPvK7dnXfx9VPapma82y0WCkUZlGHHv/2MMEDFCqVCk2bdpEx44dqV+/PsuWLTNn2GZ348YN2rVrR0hICFFRUZKAhRAihSRhC3uhxAscef8IE5pPoLixeIblJnXocHVwxTXeFcdNjrS/3p5CukJPHafX6xk5ciTr169n7Nix9OvXjwcPzDsGbQ7bt2/Hx8cHHx8ffv31V7y9va0dkhBC5BnSHZ2Lkk3JbDqziZXHV7L74m5O3z6NSWn1KQ16A1WKVaFp2ab0rNGTpmWbcvToUaZMmcLmzZsZOHAgQ4cOxcPD46l2o6OjGTJkCHv27GHJkiXUqZOVrZo0F/+5yIHLBzhw5QBXoq+gUBQ3FqdOqTrULV2XCoUrPLZrVIbvNTmZSZMm8fXXX/PTTz/RunXrLMckhBD2TpKwFSmliE+OR6/TY9Ab0k1yp0+fZtq0aSxfvpw+ffrwwQcfUKZMmaeOW7JkCUOHDiUgIIBhw4ah1z+7oyM2MZbg34OZumsqf9/7GycHJ+4n3H/4wUCHjgJOBUgyJeFh9GDkSyPxr+VPIZen78wfdfXqVXr16kVSUhKLFy+mdOnSmfyOCCFE/iJJ2IZcvnyZmTNnsmDBArp06cKoUaN4/vnnHzvm7Nmz+Pn5UbhwYX766SdKliyZZlubTm+i1+pexCXFcT/hfqaubzQYcdA5MK/dPPxe9EvzQ0N4eDi9e/emf//+jBs3DkdHu9qyWgghzErGhG1I6dKlmT59OqdOncLb25uXXnqJHj16cOjQoYfHVKhQgR07dlC3bl18fHzYuHHjY20kJify9pq36bKsCzdjbmY6AQPEJMYQnRDNeyHv0XZRWx4k/DsGnZyczLhx4+jduzeBgYFMmDBBErAQQmRA7oRtWHR0NN9++y0zZ86kVq1ajBkzhqZNmz58fdu2bfTu3Ztu3boxefJk9I562i9pz65zu4hJynwd7bS4OLpQpVgVdvXdxT83/8HPzw9HR0cWLlyIp6dnxg0IIYSQJGwP4uLiCAwMZOrUqXh5eTFmzBhat26NTqfj1q1b9O/fn3PnzuE11Iutl7dmaSOLZ3F2cKaSsRI3p91k0MBBjBkzBgcHB7O0LYQQ+YEkYTuSlJTE8uXLmTRpEgaDgYCAALp06YJer2fglwOZf3M+GMx7TV2ijsHVBvPVf74yb8NCCJEPSBK2QyaTidDQUCZNmsTt27cZOnIoH938iLvxdy1yPaPByB+D/sC7kKwBFkKIrJAkbMeUUmzfvp0BPw7gpPdJlMEy/9RODk4Mqj+Ima1nWqR9IYSwV5KE7ZxSinKzynHhnwsWvU4BpwLcGHkj3b2ahRBCPE3WkNi532/8zu3Y2xkfmAisBC4CqauW/gcUeeK4E8BO4DrgAJQA/EDvrGfr2a20fb6tmSIXQgj7J+uE7dz+y/szd2AycBl4VnGro0AwcA2oAlQHEoBEiEmIIepSVI5iFUKI/EbuhO3cnot7eJD4AD5JeaINEIl2t1sF6Ij2v8AFGAHEAlPTaEgBW1L+3guo8PjLSSqJHed3mDl6IYSwb3InbOcu/nPx8Se2Ad6ACe3O9kgmG7oF/IOWsCOAicCXwL5/D7n+4HrOghVCiHxGkrCdS92M4aH2QGfghZTHVzLZUGp9jyTgTsr50cB6tHFitKVRQgghMk+6o+1cEZcnZlalVpRMncSckMmG3B75exfAC63wRxTwJ1ANCjo/e69kIYQQj5M7YTvXqEyjx5cNZfdfvBDg/MRzqYvbnLRtDxuVaZTNxoUQIn+SO2E7V7dUXZwcnIgjLuODV6PNkk61GXACWqHdCTcCtqccVwY4BuiAmto6YUnCQgiRNZKE7Vzd0nWfHhdOz+EnHqeM9fIKWhL2RUvSh4Df0dYIvwKU0bZIbF6+ec4DFkKIfEQqZuUDwzYO4+uor0k0JVqkfR06OlTpwC89f7FI+0IIYa9kTDgfGNpwKI56y3V6uBpcGd1ktMXaF0IIeyVJOB94rshz/L+X/h9Gg9Hsbbs4uPBmtTdp7N3Y7G0LIYS9k+7ofCIxOZEX573ImTtnSDIlmaVNHTo8jB6cHnoad2d3s7QphBD5idwJ5xMGBwPh/uEUcy2Gg84hx+3p0FHQuSC/9vlVErAQQmSTJOF8xMvdi6h3oyjjXgZXR9dst+Ps4Ewx12JE9I3ghRIvZHyCEEKINEkSzme8C3lzfNBx+vr0xehoRIcuS+cbDUY6Vu3IySEnqVGihoWiFEKI/EHGhPOxqEtRfL7jczaf2YyD3kHbbSkNro6uKBSNyzRmbLOxtHiuRS5HKoQQ9kmSsODa/WuEnAwh4kIEey/u5W7cXUzKhLuzO/VL16dp2aa0eb4N5QuXt3aoQghhVyQJCyGEEFYiY8JCCCGElUgSFkIIIaxEkrAQQghhJZKEhRBCCCuRJCyEEEJYiSRhIYQQwkokCQshhBBWIklYCCGEsBJJwkIIIYSVSBIWQgghrESSsBBCCGElkoSFEEIIK5EkLIQQQliJJGEhhBDCSiQJCyGEEFYiSVgIIYSwEknCQgghhJVIEhZCCCGsRJKwEEIIYSWShIUQQggrkSQshBBCWIkkYSGEEMJKJAkLIYQQViJJWAghhLASScJCCCGElUgSFkIIIaxEkrAQQghhJZKEhRBCCCuRJCyEEEJYiSRhIYQQwkokCQshhBBW8v8BCj8VS4VmNgkAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [WARNING] Please downgrade matplotlib to matplotlib==2.2.3\n",
    "\n",
    "# 构建Graph\n",
    "G = nx.Graph()\n",
    "G.add_nodes_from(list(map(lambda x: x[0], N)))\n",
    "G.add_edges_from(E)\n",
    "# 设置Graph显示属性，包括颜色，形状，大小\n",
    "ncolor = ['r'] * 6 + ['b'] * 6 + ['g'] * 6\n",
    "nsize = [700] * 6 + [700] * 6 + [700] * 6\n",
    "# 显示Graph\n",
    "plt.figure(1)\n",
    "nx.draw(G, with_labels=True, font_weight='bold', \n",
    "        node_color=ncolor, node_size=nsize)\n",
    "# plt.savefig(\"./images/graph.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Xi(nn.Module):\n",
    "    \"\"\"实现Xi函数，输入一个batch的相邻节点特征向量对ln，返回是s*s的A矩阵\"\"\"\n",
    "    def __init__(self, ln, s):\n",
    "        super(Xi, self).__init__()\n",
    "        self.ln = ln   # 节点特征向量的维度\n",
    "        self.s = s     # 节点的个数\n",
    "        \n",
    "        # 线性网络层\n",
    "        self.linear = nn.Linear(in_features=2 * ln,\n",
    "                                out_features=s ** 2,\n",
    "                                bias=True)\n",
    "        # 激活函数\n",
    "        self.tanh = nn.Tanh()\n",
    "        \n",
    "    def forward(self, X):\n",
    "        \"\"\"\n",
    "        :param X: (N, 2*ln)，输入的节点特征及邻居节点特征concat。\n",
    "        :return out: (N, S, S)，输出用于线性变换的参数矩阵。\n",
    "        \"\"\"\n",
    "        bs = X.size()[0]\n",
    "        out = self.linear(X)\n",
    "        out = self.tanh(out)\n",
    "        out = out.view(bs, self.s, self.s)\n",
    "        return out\n",
    "\n",
    "# 实现Rou函数\n",
    "# Input : (N, ln)\n",
    "# Output : (N, S)\n",
    "class Rou(nn.Module):\n",
    "    def __init__(self, ln, s):\n",
    "        super(Rou, self).__init__()\n",
    "        self.linear = nn.Sequential(\n",
    "            nn.Linear(in_features=ln, out_features=s, bias=True),\n",
    "            nn.Tanh()\n",
    "        )\n",
    "        \n",
    "    def forward(self, X):\n",
    "        out = self.linear(X)\n",
    "        return out\n",
    "\n",
    "\n",
    "# 实现Hw函数\n",
    "class Hw(nn.Module):\n",
    "    def __init__(self, ln, s, mu=0.9):\n",
    "        super(Hw, self).__init__()\n",
    "        self.ln = ln\n",
    "        self.s = s\n",
    "        self.mu = mu\n",
    "        \n",
    "        # 初始化网络层\n",
    "        self.Xi = Xi(ln, s)\n",
    "        self.Rou = Rou(ln, s)\n",
    "    \n",
    "    def forward(self, X, H, dg_list):\n",
    "        \"\"\"\n",
    "        :param X: (N, 2 * ln)，一个节点特征向量和该节点的某一个邻接向量concat得到的向量\n",
    "        :param H: (N, s)，对应中心节点的状态向量\n",
    "        :param dg_list: (N, )，对应中心节点的度的向量\n",
    "        :return out: (N, s)\n",
    "        \"\"\"\n",
    "        if type(dg_list) == list:\n",
    "            dg_list = torch.Tensor(dg_list)\n",
    "        elif isinstance(dg_list, torch.Tensor):\n",
    "            pass\n",
    "        else:\n",
    "            raise TypeError(\"==> dg_list should be list or tensor, not {}\".format(type(dg_list)))\n",
    "        \n",
    "        A = (self.Xi(X) * self.mu / self.s) / dg_list.view(-1, 1, 1) # (N, S, S)\n",
    "        b = self.Rou(torch.chunk(X, chunks=2, dim=1)[0]) # (N, S)\n",
    "        out = torch.squeeze(torch.matmul(A, torch.unsqueeze(H, 2)),-1) + b  # (N, s, s) * (N, s) + (N, s)\n",
    "        return out    # (N, s)\n",
    "\n",
    "class AggrSum(nn.Module):\n",
    "    def __init__(self, node_num):\n",
    "        super(AggrSum, self).__init__()\n",
    "        self.V = node_num\n",
    "    \n",
    "    def forward(self, H, X_node):\n",
    "        # H : (N, s) -> (V, s)\n",
    "        # X_node : (N, )\n",
    "        mask = torch.stack([X_node] * self.V, 0)\n",
    "        mask = mask.float() - torch.unsqueeze(torch.range(0,self.V-1).float(), 1)\n",
    "        mask = (mask == 0).float()\n",
    "        # (V, N) * (N, s) -> (V, s)\n",
    "        return torch.mm(mask, H)\n",
    "\n",
    "# 实现GNN模型\n",
    "class OriLinearGNN(nn.Module):\n",
    "    \"\"\"根据论文`The graph neural network model`构建的LinearGNN模型。\"\"\"\n",
    "    def __init__(self, node_num, feat_dim, stat_dim, T):\n",
    "        \"\"\"\n",
    "        初始化模型函数。\n",
    "        \n",
    "        :param node_num: 节点数量\n",
    "        :param feat_dim: 节点特征维度\n",
    "        :param stat_dim: 节点状态维度\n",
    "        :param T       : GNN更新轮数\n",
    "        \"\"\"\n",
    "        super(OriLinearGNN, self).__init__()\n",
    "        self.node_num = node_num\n",
    "        self.embed_dim = feat_dim\n",
    "        self.stat_dim = stat_dim\n",
    "        self.T = T\n",
    "        \n",
    "        # 初始化节点的embedding，即节点特征向量 (V, ln)\n",
    "        self.node_features = nn.Parameter(\n",
    "            data=torch.randn((self.node_num, self.embed_dim), dtype=torch.float32),\n",
    "            requires_grad = True)\n",
    "        \n",
    "        # 输出层\n",
    "        self.linear = nn.Linear(feat_dim+stat_dim, 3)\n",
    "        self.softmax = nn.Softmax()\n",
    "        \n",
    "        # 实现Fw\n",
    "        self.Hw = Hw(feat_dim, stat_dim)\n",
    "        # 实现H的分组求和\n",
    "        self.Aggr = AggrSum(node_num)\n",
    "        \n",
    "    def forward(self, X_Node, X_Neis, dg_list):\n",
    "        \"\"\"前向计算函数。值得注意的是，这里输入的X_Node和N_Neis的第一个维度`N`表示边的个数。\n",
    "        比如：\n",
    "            X_Node: [0, 0, 0, 1, 1, ..., 18, 18]\n",
    "            X_Neis: [1, 2, 4, 1, 4, ..., 11, 13]\n",
    "        :param X_Node: 节点索引\n",
    "        :param X_Neis: X_node对应节点邻居的索引\n",
    "        :param dg_list: 节点的度列表\n",
    "        \"\"\"\n",
    "        \n",
    "        node_embeds = self.node_features[X_Node]  # (N, ln)\n",
    "        neis_embeds = self.node_features[X_Neis]  # (N, ln)\n",
    "        X = torch.cat((node_embeds, neis_embeds), 1)  # (N, 2 * ln)\n",
    "        \n",
    "        # 初始化节点的状态向量 (V, s)\n",
    "        node_states = Variable(torch.zeros((self.node_num, self.stat_dim), dtype=torch.float32),\n",
    "                               requires_grad = False)\n",
    "        # 循环T次计算\n",
    "        for t in range(self.T):\n",
    "            # (V, s) -> (N, s)\n",
    "            H = torch.index_select(node_states, 0, X_Node)\n",
    "            # (N, s) -> (N, s)\n",
    "            H = self.Hw(X, H, dg_list)\n",
    "            # (N, s) -> (V, s)\n",
    "            node_states = self.Aggr(H, X_Node)\n",
    "            \n",
    "        out = self.linear(torch.cat((self.node_features.data, node_states), 1))\n",
    "        out = self.softmax(out)\n",
    "        return out  # (V, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型训练和评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def CalAccuracy(output, label):\n",
    "    # output : (N, C)\n",
    "    # label : (N)\n",
    "    out = np.argmax(output, axis=1)\n",
    "    res = out - label\n",
    "    return list(res).count(0) / len(res)\n",
    "\n",
    "# 开始训练模型\n",
    "def train(node_list, edge_list, label_list, T, ndict_path=\"./node_dict.json\"):\n",
    "    # 生成node-index字典\n",
    "    if os.path.exists(ndict_path):\n",
    "        with open(ndict_path, \"r\") as fp:\n",
    "            node_dict = json.load(fp)\n",
    "    else:\n",
    "        node_dict = dict([(node, ind) for ind, node in enumerate(node_list)])\n",
    "        node_dict = {\"stoi\" : node_dict,\n",
    "                     \"itos\" : node_list}\n",
    "        with open(ndict_path, \"w\") as fp:\n",
    "            json.dump(node_dict, fp)\n",
    "\n",
    "    # 现在需要生成两个向量\n",
    "    # 第一个向量类似于\n",
    "    #   [0, 0, 0, 1, 1, ..., 18, 18]\n",
    "    # 其中的值表示节点的索引，连续相同索引的个数为该节点的度\n",
    "    # 第二个向量类似于\n",
    "    #   [1, 2, 4, 1, 4, ..., 11, 13]\n",
    "    # 与第一个向量一一对应，表示第一个向量节点的邻居节点\n",
    "\n",
    "    # 首先统计得到节点的度\n",
    "    Degree = dict()\n",
    "    for n1, n2 in edge_list:\n",
    "        # 边的第一个节点的邻接节点为第二个节点\n",
    "        if n1 in Degree:\n",
    "            Degree[n1].add(n2)\n",
    "        else:\n",
    "            Degree[n1] = {n2}\n",
    "        # 边的第二个节点的邻接节点为第一个节点\n",
    "        if n2 in Degree:\n",
    "            Degree[n2].add(n1)\n",
    "        else:\n",
    "            Degree[n2] = {n1}\n",
    "    \n",
    "    # 然后生成两个向量\n",
    "    node_inds = []\n",
    "    node_neis = []\n",
    "    for n in node_list:\n",
    "        node_inds += [node_dict[\"stoi\"][n]] * len(Degree[n])\n",
    "        node_neis += list(map(lambda x: node_dict[\"stoi\"][x],list(Degree[n])))\n",
    "    \n",
    "    # 生成度向量\n",
    "    dg_list = list(map(lambda x: len(Degree[node_dict[\"itos\"][x]]), node_inds))\n",
    "    \n",
    "    # 准备训练集和测试集\n",
    "    train_node_list = [0,1,2,6,7,8,12,13,14]\n",
    "    train_node_label = [0,0,0,1,1,1,2,2,2]\n",
    "    test_node_list = [3,4,5,9,10,11,15,16,17]\n",
    "    test_node_label = [0,0,0,1,1,1,2,2,2]\n",
    "    \n",
    "    # 开始训练\n",
    "    model = OriLinearGNN(node_num=len(node_list),\n",
    "                         feat_dim=2,\n",
    "                         stat_dim=2,\n",
    "                         T=T)\n",
    "    optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=0.01)\n",
    "    criterion = nn.CrossEntropyLoss(size_average=True)\n",
    "    \n",
    "    min_loss = float('inf')\n",
    "    train_loss_list = []\n",
    "    train_acc_list = []\n",
    "    test_acc_list = []\n",
    "    node_inds_tensor = Variable(torch.Tensor(node_inds).long())\n",
    "    node_neis_tensor = Variable(torch.Tensor(node_neis).long())\n",
    "    train_label = Variable(torch.Tensor(train_node_label).long())\n",
    "    for ep in range(500):\n",
    "        # 运行模型得到结果\n",
    "        with torch.autograd.set_detect_anomaly(True):\n",
    "            res = model(node_inds_tensor, node_neis_tensor, dg_list) # (V, 3)\n",
    "            train_res = torch.index_select(res, 0, torch.Tensor(train_node_list).long())\n",
    "            test_res = torch.index_select(res, 0, torch.Tensor(test_node_list).long())\n",
    "            loss = criterion(input=train_res,\n",
    "                             target=train_label)\n",
    "            loss_val = loss.item()\n",
    "            train_acc = CalAccuracy(train_res.cpu().detach().numpy(), np.array(train_node_label))\n",
    "            test_acc = CalAccuracy(test_res.cpu().detach().numpy(), np.array(test_node_label))\n",
    "            # 更新梯度\n",
    "            optimizer.zero_grad()\n",
    "            loss.backward(retain_graph=True)\n",
    "            optimizer.step()\n",
    "        # 保存loss和acc\n",
    "        train_loss_list.append(loss_val)\n",
    "        test_acc_list.append(test_acc)\n",
    "        train_acc_list.append(train_acc)\n",
    "        \n",
    "        if loss_val < min_loss:\n",
    "            min_loss = loss_val\n",
    "        print(\"==> [Epoch {}] : loss {:.4f}, min_loss {:.4f}, train_acc {:.3f}, test_acc {:.3f}\".format(ep, loss_val, min_loss, train_acc, test_acc))\n",
    "    return train_loss_list, train_acc_list, test_acc_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "train_loss, train_acc, test_acc = train(node_list=list(map(lambda x:x[0], N)),\n",
    "                                        edge_list=E,\n",
    "                                        label_list=list(map(lambda x:x[1], N)),\n",
    "                                        T=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 画出loss和acc曲线"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "epochs = len(train_loss)\n",
    "plt.figure()\n",
    "# 画出train_loss\n",
    "plt.subplot(3,1,1)\n",
    "plt.plot(range(epochs), train_loss)\n",
    "plt.xlim((0,epochs))    # 设置x轴的范围\n",
    "plt.ylabel('loss')      # 设置y周标签\n",
    "plt.yticks(np.arange(0,1.5,0.2))  # 设置y轴刻度\n",
    "\n",
    "# 画出train_acc\n",
    "plt.subplot(3,1,2)\n",
    "plt.plot(range(epochs), train_acc)\n",
    "plt.xlim((0,epochs))\n",
    "plt.ylim((0,1))\n",
    "plt.ylabel('train_acc')\n",
    "plt.yticks(np.arange(0,1,0.1))\n",
    "# 画出test_acc\n",
    "plt.subplot(3,1,3)\n",
    "plt.plot(range(epochs), test_acc)\n",
    "plt.xlim((0,epochs))\n",
    "plt.ylim((0,1))\n",
    "plt.xlabel('epoch')\n",
    "plt.ylabel('test_acc')\n",
    "plt.yticks(np.arange(0,1,0.1))\n",
    "\n",
    "# 保存图片\n",
    "# plt.savefig(\"./images/result.png\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
